diff --git a/gradle.properties b/gradle.properties index 783ab5c09..d096bc6e0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina -version=1.9.0-SNAPSHOT +version=2.0.0-SNAPSHOT # Client Native Version clientNativeVersion=1.0.0-SNAPSHOT diff --git a/openapi-client-native/ballerina-tests/client.bal b/openapi-client-native/ballerina-tests/client.bal index 2e29bd76c..4b220855e 100644 --- a/openapi-client-native/ballerina-tests/client.bal +++ b/openapi-client-native/ballerina-tests/client.bal @@ -25,104 +25,103 @@ public isolated client class Client { } @MethodImpl {name: "getAlbumsIdImpl"} - resource isolated function get albums/[string id](typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; + resource isolated function get albums/[string id](map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; - private isolated function getAlbumsIdImpl(string id, typedesc targetType) returns OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsIdImpl(string id, map headers, typedesc targetType) returns OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums/${getEncodedUri(id)}`; - return self.clientEp->get(resourcePath, targetType = targetType); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } @MethodImpl {name: "getAlbumsImpl"} - resource isolated function get albums(string genre = "Rock", typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function get albums(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; - private isolated function getAlbumsImpl(string genre, typedesc targetType) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsImpl(map headers, typedesc targetType, *GetAlbumsQueries queries) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums`; - map queryParam = {"genre": genre}; - resourcePath = resourcePath + check getPathForQueryParam(queryParam); - return self.clientEp->get(resourcePath, targetType = targetType); + resourcePath = resourcePath + check getPathForQueryParam(queries); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } @MethodImpl {name: "getAlbumsAllImpl"} - resource isolated function get albums\-all(typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function get albumsAll(map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; @MethodImpl {name: "getAlbumsAllImpl"} - remote isolated function getAlbumsAll(typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbumsAll(map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function getAlbumsAllImpl(typedesc targetType) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsAllImpl(map headers, typedesc targetType) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums-all`; - return self.clientEp->get(resourcePath, targetType = targetType); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } @MethodImpl {name: "postAlbumsImpl"} - resource isolated function post albums(http:Request req, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function post albums(http:Request req, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; @MethodImpl {name: "postAlbumsImpl"} - remote isolated function postAlbums(http:Request req, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function postAlbums(http:Request req, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function postAlbumsImpl(http:Request req, typedesc targetType) returns CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error { + private isolated function postAlbumsImpl(http:Request req, map headers, typedesc targetType) returns CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error { string resourcePath = string `/albums`; - return self.clientEp->post(resourcePath, req, targetType = targetType); + return self.clientEp->post(resourcePath, req, headers, targetType = targetType); } @MethodImpl {name: "postAlbums1Impl"} - resource isolated function post albums\-1/[string a]/[int b](http:Request req, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; + resource isolated function post albums1/[string a]/[int b](http:Request req, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; @MethodImpl {name: "postAlbums1Impl"} - remote isolated function postAlbums1(string a, int b, http:Request req, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function postAlbums1(string a, int b, http:Request req, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function postAlbums1Impl(string a, int b, http:Request req, typedesc targetType) returns CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error { + private isolated function postAlbums1Impl(string a, int b, http:Request req, map headers, typedesc targetType) returns CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error { string resourcePath = string `/albums`; - return self.clientEp->post(resourcePath, req, targetType = targetType); + return self.clientEp->post(resourcePath, req, headers, targetType = targetType); } @MethodImpl {name: "postAlbumsAllImpl"} - resource isolated function post albums\-all/[string a](Album[] albums, string? query = (), typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; + resource isolated function post albumsAll/[string a](Album[] albums, map headers = {}, typedesc targetType = <>, *PostAlbumsAllQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; @MethodImpl {name: "postAlbumsAllImpl"} - remote isolated function postAlbumsAll(string a, Album[] albums, string? query = (), typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function postAlbumsAll(string a, Album[] albums, map headers = {}, typedesc targetType = <>, *PostAlbumsAllQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function postAlbumsAllImpl(string a, Album[] albums, string? query, typedesc targetType) returns CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error { + private isolated function postAlbumsAllImpl(string a, Album[] albums, map headers, typedesc targetType, *PostAlbumsAllQueries queries) returns CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error { string resourcePath = string `/albums-all`; - return self.clientEp->post(resourcePath, albums, targetType = targetType); + return self.clientEp->post(resourcePath, albums, headers, targetType = targetType); } @MethodImpl {name: "postAlbumsAll1Impl"} - resource isolated function post albums\-all\-1(http:Request req, string? query = (), typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function post albumsAll1(http:Request req, map headers = {}, typedesc targetType = <>, *PostAlbumsAllQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; @MethodImpl {name: "postAlbumsAll1Impl"} - remote isolated function postAlbumsAll1(http:Request req, string? query = (), typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function postAlbumsAll1(http:Request req, map headers = {}, typedesc targetType = <>, *PostAlbumsAllQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function postAlbumsAll1Impl(http:Request req, string? query, typedesc targetType) returns CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error { + private isolated function postAlbumsAll1Impl(http:Request req, map headers, typedesc targetType, *PostAlbumsAllQueries queries) returns CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error { string resourcePath = string `/albums-all`; - return self.clientEp->post(resourcePath, req, targetType = targetType); + return self.clientEp->post(resourcePath, req, headers, targetType = targetType); } @MethodImpl {name: "postAlbumsAll2Impl"} - resource isolated function post albums\-all\-2(Album[] albums, http:Request req, string? query = (), typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function post albumsAll2(Album[] albums, http:Request req, map headers = {}, typedesc targetType = <>, *PostAlbumsAllQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; @MethodImpl {name: "postAlbumsAll2Impl"} - remote isolated function postAlbumsAll2(Album[] albums, http:Request req, string? query = (), typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function postAlbumsAll2(Album[] albums, http:Request req, map headers = {}, typedesc targetType = <>, *PostAlbumsAllQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function postAlbumsAll2Impl(Album[] albums, http:Request req, string? query, typedesc targetType) returns CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error { + private isolated function postAlbumsAll2Impl(Album[] albums, http:Request req, map headers, typedesc targetType, *PostAlbumsAllQueries queries) returns CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error { string resourcePath = string `/albums-all`; - return self.clientEp->post(resourcePath, albums, targetType = targetType); + return self.clientEp->post(resourcePath, albums, headers, targetType = targetType); } @MethodImpl {name: "getAlbumsIdImpl"} - remote isolated function getAlbumsId(string id, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbumsId(string id, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; @MethodImpl {name: "getAlbumsImpl"} - remote isolated function getAlbums(string genre = "Rock", typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbums(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; @MethodImpl {name: "getAlbumsImpl1"} - remote isolated function getAlbums1(string genre = "Rock", typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbums1(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - remote isolated function getAlbums2(string genre = "Rock", typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbums2(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; @MethodImpl {name: "getAlbumsImpl3"} - remote isolated function getAlbums3(string genre = "Rock", typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbums3(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function getAlbumsImpl3(string genre, string 'type) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsImpl3(map headers, *GetAlbumsQueries queries) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { return { body: [], headers: { diff --git a/openapi-client-native/ballerina-tests/tests/test.bal b/openapi-client-native/ballerina-tests/tests/test.bal index a2ecf0aef..7ed27a4ed 100644 --- a/openapi-client-native/ballerina-tests/tests/test.bal +++ b/openapi-client-native/ballerina-tests/tests/test.bal @@ -52,7 +52,7 @@ function testResourceMethod3() returns error? { @test:Config {} function testResourceMethod4() returns error? { - OkAlbumArray res = check albumClient->/albums.get("Hard Rock"); + OkAlbumArray res = check albumClient->/albums.get(genre = "Hard Rock"); Album[] expected = [ {id: "2", name: "Back in Black", artist: "AC/DC", genre: "Hard Rock"} ]; @@ -62,7 +62,7 @@ function testResourceMethod4() returns error? { @test:Config {} function testResourceMethod5() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->/albums.get("Hard Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->/albums.get(genre = "Hard Rock"); OkAlbumArray expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -79,7 +79,7 @@ function testResourceMethod5() { @test:Config {} function testResourceMethod6() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->/albums.get("Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->/albums.get(genre = "Rock"); NotFoundErrorMessage expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -96,7 +96,7 @@ function testResourceMethod6() { @test:Config {} function testResourceMethod7() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->/albums\-all; + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->/albumsAll; OkAlbumArray expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -198,7 +198,7 @@ function testResourceMethod11() { http:Request req = new; Album newAlbum = {id: "5", name: "The Wall", artist: "Pink Floyd", genre: "Progressive Rock"}; req.setJsonPayload(newAlbum); - CreatedAlbum|error res = albumClient->/albums\-1/a/[3].post(req); + CreatedAlbum|error res = albumClient->/albums1/a/[3].post(req); CreatedAlbum expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -230,7 +230,7 @@ function testResourceMethod12() { http:Request req = new; Album existingAlbum = {id: "1", name: "The Dark Side of the Moon", artist: "Pink Floyd", genre: "Progressive Rock"}; req.setJsonPayload(existingAlbum); - CreatedAlbum|ConflictAlbum|error res = albumClient->/albums\-1/a/[3].post(req); + CreatedAlbum|ConflictAlbum|error res = albumClient->/albums1/a/[3].post(req); ConflictAlbum expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -259,7 +259,7 @@ function testResourceMethod13() { http:Request req = new; json notAnAlbum = {id: "4", name: "The Wall", artist: "Pink Floyd"}; req.setJsonPayload(notAnAlbum); - CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error res = albumClient->/albums\-1/a/[3].post(req); + CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error res = albumClient->/albums1/a/[3].post(req); test:assertTrue(res is BadRequestErrorPayload); res = albumClient->postAlbums1("a", 3, req); @@ -269,7 +269,7 @@ function testResourceMethod13() { @test:Config {} function testResourceMethod14() { Album[] newAlbums = [{id: "6", name: "The Wall", artist: "Pink Floyd", genre: "Progressive Rock"}]; - CreatedAlbumArray|error res = albumClient->/albums\-all/a.post(newAlbums, query = "q"); + CreatedAlbumArray|error res = albumClient->/albumsAll/a.post(newAlbums, query = "q"); CreatedAlbumArray expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -297,7 +297,7 @@ function testResourceMethod14() { function testResourceMethod15() { Album[] existingAlbums = [{id: "1", name: "The Dark Side of the Moon", artist: "Pink Floyd", genre: "Progressive Rock"}]; - CreatedAlbumArray|ConflictAlbum|error res = albumClient->/albums\-all/a.post(existingAlbums); + CreatedAlbumArray|ConflictAlbum|error res = albumClient->/albumsAll/a.post(existingAlbums); ConflictAlbum expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -326,7 +326,7 @@ function testResourceMethod16() { http:Request req = new; Album[] newAlbums = [{id: "7", name: "The Wall", artist: "Pink Floyd", genre: "Progressive Rock"}]; req.setJsonPayload(newAlbums); - CreatedAlbumArray|error res = albumClient->/albums\-all\-1.post(req, query = "q"); + CreatedAlbumArray|error res = albumClient->/albumsAll1.post(req, query = "q"); CreatedAlbumArray expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -358,7 +358,7 @@ function testResourceMethod17() { http:Request req = new; Album[] existingAlbums = [{id: "1", name: "The Dark Side of the Moon", artist: "Pink Floyd", genre: "Progressive Rock"}]; req.setJsonPayload(existingAlbums); - CreatedAlbumArray|ConflictAlbum|error res = albumClient->/albums\-all\-1.post(req, query = "q"); + CreatedAlbumArray|ConflictAlbum|error res = albumClient->/albumsAll1.post(req, query = "q"); ConflictAlbum expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -387,7 +387,7 @@ function testResourceMethod18() { http:Request req = new; json notAnAlbum = [{id: "4", name: "The Wall", artist: "Pink Floyd"}]; req.setJsonPayload(notAnAlbum); - CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error res = albumClient->/albums\-all\-1.post(req, query = "q"); + CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error res = albumClient->/albumsAll1.post(req, query = "q"); test:assertTrue(res is BadRequestErrorPayload); res = albumClient->postAlbumsAll1(req, query = "q"); @@ -443,7 +443,7 @@ function testRemoteMethod3() returns error? { @test:Config {} function testRemoteMethod4() returns error? { - OkAlbumArray res = check albumClient->getAlbums("Hard Rock"); + OkAlbumArray res = check albumClient->getAlbums(genre = "Hard Rock"); Album[] expected = [ {id: "2", name: "Back in Black", artist: "AC/DC", genre: "Hard Rock"} ]; @@ -453,7 +453,7 @@ function testRemoteMethod4() returns error? { @test:Config {} function testRemoteMethod5() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums("Hard Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums(genre = "Hard Rock"); OkAlbumArray expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -470,7 +470,7 @@ function testRemoteMethod5() { @test:Config {} function testRemoteMethod6() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums("Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums(genre = "Rock"); NotFoundErrorMessage expected = { mediaType: "application/json", headers: {user\-id: "user-1", req\-id: 1}, @@ -487,7 +487,7 @@ function testRemoteMethod6() { @test:Config {} function testInvalidMethodInvocation() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums1("Hard Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums1(genre = "Hard Rock"); if res is error { test:assertTrue(res is ClientMethodInvocationError); test:assertEquals(res.message(), "client method invocation failed: No such method: getAlbumsImpl1"); @@ -498,7 +498,7 @@ function testInvalidMethodInvocation() { @test:Config {} function testAnnotationNotFound() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums2("Hard Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums2(genre = "Hard Rock"); if res is error { test:assertTrue(res is ClientMethodInvocationError); test:assertEquals(res.message(), "error in invoking client remote method: Method implementation annotation not found"); @@ -509,11 +509,11 @@ function testAnnotationNotFound() { @test:Config {} function testInvalidImplFunctionSignature() { - OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums3("Hard Rock"); + OkAlbumArray|NotFoundErrorMessage|error res = albumClient->getAlbums3(genre = "Hard Rock"); if res is error { test:assertTrue(res is ClientMethodInvocationError); test:assertTrue(res.message().includes("client method invocation failed: java.lang.ClassCastException: " + - "class io.ballerina.runtime.internal.values.TypedescValueImpl cannot be cast to class io.ballerina.runtime.api.values.BString")); + "class io.ballerina.runtime.internal.values.TypedescValueImpl cannot be cast to class io.ballerina.runtime.internal.values.MapValueImpl")); } else { test:assertFail("invalid response type"); } diff --git a/openapi-client-native/ballerina-tests/types.bal b/openapi-client-native/ballerina-tests/types.bal index 6071c3c93..277e467c4 100644 --- a/openapi-client-native/ballerina-tests/types.bal +++ b/openapi-client-native/ballerina-tests/types.bal @@ -62,3 +62,16 @@ public type NotFoundErrorMessage record {| ErrorMessage body; record {|int req\-id; string user\-id;|} headers; |}; + +public type GetAlbumsQueries record { + string genre = "Rock"; +}; + +public type GetAlbumsQueriesClosed record {| + string genre; + string 'type; +|}; + +public type PostAlbumsAllQueries record { + string query?; +}; diff --git a/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/ClientGenerationTests.java b/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/ClientGenerationTests.java index 8fd046a72..a4b323daa 100644 --- a/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/ClientGenerationTests.java +++ b/openapi-integration-tests/src/test/java/io/ballerina/openapi/cmd/ClientGenerationTests.java @@ -19,7 +19,6 @@ import io.ballerina.openapi.OpenAPITest; import io.ballerina.openapi.TestUtil; -import org.apache.commons.io.FileUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -29,6 +28,8 @@ import java.nio.file.Paths; import java.util.LinkedList; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static io.ballerina.openapi.TestUtil.DISTRIBUTIONS_DIR; import static io.ballerina.openapi.TestUtil.RESOURCES_PATH; @@ -40,6 +41,7 @@ public class ClientGenerationTests extends OpenAPITest { public static final String DISTRIBUTION_FILE_NAME = DISTRIBUTIONS_DIR.toString(); public static final Path TEST_RESOURCE = Paths.get(RESOURCES_PATH.toString() + "/client"); public static final Path EXPECTED_RESOURCE = Paths.get("src/test/resources/client"); + private static final String LINE_SEPARATOR = System.lineSeparator(); @Test(description = "Client generation with resource functions") public void clientWithResourceFunction() throws IOException, InterruptedException { @@ -101,14 +103,11 @@ public void resourceClientWithStatusCodeBinding() throws IOException, Interrupte boolean successful = TestUtil.executeOpenAPI(DISTRIBUTION_FILE_NAME, projectGenPath, buildArgs); Assert.assertTrue(Files.exists(projectGenPath.resolve("Ballerina.toml"))); Assert.assertTrue(Files.exists(projectGenPath.resolve("client.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("client.bal").toFile(), - projectExpectedPath.resolve("client_resource.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "client.bal", projectExpectedPath, "client_resource.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("types.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("types.bal").toFile(), - projectExpectedPath.resolve("types.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "types.bal", projectExpectedPath, "types.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("utils.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("utils.bal").toFile(), - projectExpectedPath.resolve("utils.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "utils.bal", projectExpectedPath, "utils.bal"); } @Test(description = "`--status-code-binding` and `--client-methods remote` options with client") @@ -127,14 +126,11 @@ public void remoteClientWithStatusCodeBinding() throws IOException, InterruptedE boolean successful = TestUtil.executeOpenAPI(DISTRIBUTION_FILE_NAME, projectGenPath, buildArgs); Assert.assertTrue(Files.exists(projectGenPath.resolve("Ballerina.toml"))); Assert.assertTrue(Files.exists(projectGenPath.resolve("client.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("client.bal").toFile(), - projectExpectedPath.resolve("client_resource.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "client.bal", projectExpectedPath, "client_remote.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("types.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("types.bal").toFile(), - projectExpectedPath.resolve("types.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "types.bal", projectExpectedPath, "types.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("utils.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("utils.bal").toFile(), - projectExpectedPath.resolve("utils.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "utils.bal", projectExpectedPath, "utils.bal"); } @Test(description = "`--status-code-binding` option with service") @@ -146,13 +142,12 @@ public void serviceWithStatusCodeBinding() throws IOException, InterruptedExcept buildArgs.add("--mode"); buildArgs.add("service"); buildArgs.add("--status-code-binding"); - Path projectGenPath = Paths.get(TEST_RESOURCE + "/project-01"); - Path projectExpectedPath = Paths.get(EXPECTED_RESOURCE + "/project-01"); + Path projectGenPath = Paths.get(TEST_RESOURCE + "/project-05"); + Path projectExpectedPath = Paths.get(EXPECTED_RESOURCE + "/project-05"); boolean successful = TestUtil.executeOpenAPI(DISTRIBUTION_FILE_NAME, projectGenPath, buildArgs); Assert.assertFalse(Files.exists(projectGenPath.resolve("service.bal"))); Assert.assertTrue(Files.exists(projectGenPath.resolve("Ballerina.toml"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("Ballerina.toml").toFile(), - projectExpectedPath.resolve("Ballerina.toml").toFile(), "UTF-8"); + compareFiles(projectGenPath, "Ballerina.toml", projectExpectedPath, "Ballerina.toml"); } @Test(description = "`--status-code-binding` option without any mode") @@ -167,17 +162,13 @@ public void commonWithStatusCodeBinding() throws IOException, InterruptedExcepti boolean successful = TestUtil.executeOpenAPI(DISTRIBUTION_FILE_NAME, projectGenPath, buildArgs); Assert.assertTrue(Files.exists(projectGenPath.resolve("Ballerina.toml"))); Assert.assertTrue(Files.exists(projectGenPath.resolve("client.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("client.bal").toFile(), - projectExpectedPath.resolve("client_resource.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "client.bal", projectExpectedPath, "client_resource.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("types.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("types.bal").toFile(), - projectExpectedPath.resolve("types.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "types.bal", projectExpectedPath, "types_all.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("utils.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("utils.bal").toFile(), - projectExpectedPath.resolve("utils.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "utils.bal", projectExpectedPath, "utils_all.bal"); Assert.assertTrue(Files.exists(projectGenPath.resolve("openapi_service.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("openapi_service.bal").toFile(), - projectExpectedPath.resolve("openapi_service.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "openapi_service.bal", projectExpectedPath, "openapi_service.bal"); } @Test(description = "`--status-code-binding` without `Ballerina.toml`") @@ -191,7 +182,20 @@ public void nonBallerinaPackageWithStatusCodeBinding() throws IOException, Inter Path projectExpectedPath = Paths.get(EXPECTED_RESOURCE + "/project-expected"); boolean successful = TestUtil.executeOpenAPI(DISTRIBUTION_FILE_NAME, projectGenPath, buildArgs); Assert.assertTrue(Files.exists(projectGenPath.resolve("client.bal"))); - FileUtils.contentEqualsIgnoreEOL(projectGenPath.resolve("client.bal").toFile(), - projectExpectedPath.resolve("client_normal.bal").toFile(), "UTF-8"); + compareFiles(projectGenPath, "client.bal", projectExpectedPath, "client_normal.bal"); + } + + /** + * Compare two files. + */ + private void compareFiles(Path genPath, String generatedFileName, Path expectedPath, String expectedFileName) + throws IOException { + Stream expectedFile = Files.lines(expectedPath.resolve(expectedFileName)); + String expectedContent = expectedFile.collect(Collectors.joining(LINE_SEPARATOR)); + Stream generatedFile = Files.lines(genPath.resolve(generatedFileName)); + String generatedContent = generatedFile.collect(Collectors.joining(LINE_SEPARATOR)); + generatedContent = generatedContent.trim().replaceAll("\\s+", ""); + expectedContent = expectedContent.trim().replaceAll("\\s+", ""); + Assert.assertEquals(generatedContent, expectedContent); } } diff --git a/openapi-integration-tests/src/test/resources/client/project-05/Ballerina.toml b/openapi-integration-tests/src/test/resources/client/project-05/Ballerina.toml new file mode 100644 index 000000000..13e75a1c2 --- /dev/null +++ b/openapi-integration-tests/src/test/resources/client/project-05/Ballerina.toml @@ -0,0 +1,8 @@ +[package] +org = "openapi_client_test" +name = "project" +version = "0.1.0" + +[build-options] +observabilityIncluded = true + diff --git a/openapi-integration-tests/src/test/resources/client/project-05/openapi.yaml b/openapi-integration-tests/src/test/resources/client/project-05/openapi.yaml new file mode 100644 index 000000000..7cb94493c --- /dev/null +++ b/openapi-integration-tests/src/test/resources/client/project-05/openapi.yaml @@ -0,0 +1,208 @@ +openapi: 3.0.1 +info: + title: Api + version: 0.1.0 +servers: + - url: "{server}:{port}/api" + variables: + server: + default: http://localhost + port: + default: "9090" +paths: + /albums/{id}: + get: + operationId: getAlbumsId + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + headers: + req-id: + required: true + schema: + type: integer + format: int64 + user-id: + required: true + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Album' + "404": + description: NotFound + headers: + req-id: + required: true + schema: + type: integer + format: int64 + user-id: + required: true + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' + /albums: + get: + operationId: getAlbums + parameters: + - name: genre + in: query + required: true + schema: + type: string + responses: + "200": + description: Ok + headers: + req-id: + required: true + schema: + type: integer + format: int64 + user-id: + required: true + schema: + type: string + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Album' + "404": + description: NotFound + headers: + req-id: + required: true + schema: + type: integer + format: int64 + user-id: + required: true + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' + post: + operationId: postAlbums + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Album' + required: true + responses: + "201": + description: Created + headers: + req-id: + required: true + schema: + type: integer + format: int64 + user-id: + required: true + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/Album' + "409": + description: Conflict + headers: + req-id: + required: true + schema: + type: integer + format: int64 + user-id: + required: true + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorMessage' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' +components: + schemas: + Album: + required: + - artist + - genre + - id + - name + type: object + properties: + id: + type: string + name: + type: string + artist: + type: string + genre: + type: string + additionalProperties: false + ErrorMessage: + required: + - message + type: object + properties: + message: + type: string + additionalProperties: + type: string + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + timestamp: + type: string + status: + type: integer + format: int64 + reason: + type: string + message: + type: string + path: + type: string + method: + type: string diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/client_normal.bal b/openapi-integration-tests/src/test/resources/client/project-expected/client_normal.bal index fc392e38d..56f971307 100644 --- a/openapi-integration-tests/src/test/resources/client/project-expected/client_normal.bal +++ b/openapi-integration-tests/src/test/resources/client/project-expected/client_normal.bal @@ -38,26 +38,29 @@ public isolated client class Client { return; } - # + return - Ok - resource isolated function get albums(string genre) returns Album[]|error { + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - Ok + resource isolated function get albums(map headers = {}, *GetAlbumsQueries queries) returns Album[]|error { string resourcePath = string `/albums`; - map queryParam = {"genre": genre}; - resourcePath = resourcePath + check getPathForQueryParam(queryParam); - return self.clientEp->get(resourcePath); + resourcePath = resourcePath + check getPathForQueryParam(queries); + return self.clientEp->get(resourcePath, headers); } - # + return - Ok - resource isolated function get albums/[string id]() returns Album|error { + # + headers - Headers to be sent with the request + # + return - Ok + resource isolated function get albums/[string id](map headers = {}) returns Album|error { string resourcePath = string `/albums/${getEncodedUri(id)}`; - return self.clientEp->get(resourcePath); + return self.clientEp->get(resourcePath, headers); } - # + return - Created - resource isolated function post albums(Album payload) returns Album|error { + # + headers - Headers to be sent with the request + # + return - Created + resource isolated function post albums(Album payload, map headers = {}) returns Album|error { string resourcePath = string `/albums`; http:Request request = new; json jsonBody = payload.toJson(); request.setPayload(jsonBody, "application/json"); - return self.clientEp->post(resourcePath, request); + return self.clientEp->post(resourcePath, request, headers); } } diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/client_remote.bal b/openapi-integration-tests/src/test/resources/client/project-expected/client_remote.bal index a48e44072..58be12f8d 100644 --- a/openapi-integration-tests/src/test/resources/client/project-expected/client_remote.bal +++ b/openapi-integration-tests/src/test/resources/client/project-expected/client_remote.bal @@ -53,35 +53,38 @@ public isolated client class Client { return; } - # + return - Ok + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request + # + return - Ok @MethodImpl {name: "getAlbumsImpl"} - remote isolated function getAlbums(string genre, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbums(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + # + headers - Headers to be sent with the request # + return - Ok @MethodImpl {name: "getAlbumsIdImpl"} - remote isolated function getAlbumsId(string id, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function getAlbumsId(string id, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function getAlbumsIdImpl(string id, typedesc targetType) returns OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsIdImpl(string id, map headers, typedesc targetType) returns OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums/${getEncodedUri(id)}`; - return self.clientEp->get(resourcePath, targetType = targetType); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } - private isolated function getAlbumsImpl(string genre, typedesc targetType) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsImpl(map headers, typedesc targetType, *GetAlbumsQueries queries) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums`; - map queryParam = {"genre": genre}; - resourcePath = resourcePath + check getPathForQueryParam(queryParam); - return self.clientEp->get(resourcePath, targetType = targetType); + resourcePath = resourcePath + check getPathForQueryParam(queries); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } + # + headers - Headers to be sent with the request # + return - Created @MethodImpl {name: "postAlbumsImpl"} - remote isolated function postAlbums(Album payload, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; + remote isolated function postAlbums(Album payload, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external; - private isolated function postAlbumsImpl(Album payload, typedesc targetType) returns CreatedAlbum|ConflictErrorMessage|BadRequestErrorPayload|error { + private isolated function postAlbumsImpl(Album payload, map headers, typedesc targetType) returns CreatedAlbum|ConflictErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums`; http:Request request = new; json jsonBody = payload.toJson(); request.setPayload(jsonBody, "application/json"); - return self.clientEp->post(resourcePath, request, targetType = targetType); + return self.clientEp->post(resourcePath, request, headers, targetType = targetType); } } diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/client_resource.bal b/openapi-integration-tests/src/test/resources/client/project-expected/client_resource.bal index e1425c5fc..820769cd4 100644 --- a/openapi-integration-tests/src/test/resources/client/project-expected/client_resource.bal +++ b/openapi-integration-tests/src/test/resources/client/project-expected/client_resource.bal @@ -53,35 +53,38 @@ public isolated client class Client { return; } - # + return - Ok + # + headers - Headers to be sent with the request + # + return - Ok @MethodImpl {name: "getAlbumsIdImpl"} - resource isolated function get albums/[string id](typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; + resource isolated function get albums/[string id](map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResource"} external; + # + headers - Headers to be sent with the request + # + queries - Queries to be sent with the request # + return - Ok @MethodImpl {name: "getAlbumsImpl"} - resource isolated function get albums(string genre, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function get albums(map headers = {}, typedesc targetType = <>, *GetAlbumsQueries queries) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + # + headers - Headers to be sent with the request # + return - Created @MethodImpl {name: "postAlbumsImpl"} - resource isolated function post albums(Album payload, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; + resource isolated function post albums(Album payload, map headers = {}, typedesc targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external; - private isolated function getAlbumsIdImpl(string id, typedesc targetType) returns OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsIdImpl(string id, map headers, typedesc targetType) returns OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums/${getEncodedUri(id)}`; - return self.clientEp->get(resourcePath, targetType = targetType); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } - private isolated function getAlbumsImpl(string genre, typedesc targetType) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { + private isolated function getAlbumsImpl(map headers, typedesc targetType, *GetAlbumsQueries queries) returns OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums`; - map queryParam = {"genre": genre}; - resourcePath = resourcePath + check getPathForQueryParam(queryParam); - return self.clientEp->get(resourcePath, targetType = targetType); + resourcePath = resourcePath + check getPathForQueryParam(queries); + return self.clientEp->get(resourcePath, headers, targetType = targetType); } - private isolated function postAlbumsImpl(Album payload, typedesc targetType) returns CreatedAlbum|ConflictErrorMessage|BadRequestErrorPayload|error { + private isolated function postAlbumsImpl(Album payload, map headers, typedesc targetType) returns CreatedAlbum|ConflictErrorMessage|BadRequestErrorPayload|error { string resourcePath = string `/albums`; http:Request request = new; json jsonBody = payload.toJson(); request.setPayload(jsonBody, "application/json"); - return self.clientEp->post(resourcePath, request, targetType = targetType); + return self.clientEp->post(resourcePath, request, headers, targetType = targetType); } } diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/types.bal b/openapi-integration-tests/src/test/resources/client/project-expected/types.bal index adae69706..898363da9 100644 --- a/openapi-integration-tests/src/test/resources/client/project-expected/types.bal +++ b/openapi-integration-tests/src/test/resources/client/project-expected/types.bal @@ -9,15 +9,6 @@ public type CreatedAlbum record {| record {|int req\-id; string user\-id;|} headers; |}; -public type ErrorPayload record { - string timestamp; - int status; - string reason; - string message; - string path; - string method; -}; - public type BadRequestErrorPayload record {| *http:BadRequest; ErrorPayload body; @@ -30,16 +21,6 @@ public type OkAlbumArray record {| record {|int req\-id; string user\-id;|} headers; |}; -# Provides settings related to HTTP/1.x protocol. -public type ClientHttp1Settings record {| - # Specifies whether to reuse a connection for multiple requests - http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; - # The chunking behaviour of the request - http:Chunking chunking = http:CHUNKING_AUTO; - # Proxy server related options - ProxyConfig proxy?; -|}; - public type Album record {| string id; string name; @@ -47,17 +28,10 @@ public type Album record {| string genre; |}; -public type ConflictErrorMessage record {| - *http:Conflict; - ErrorMessage body; - record {|int req\-id; string user\-id;|} headers; -|}; - -public type OkAlbum record {| - *http:Ok; - Album body; - record {|int req\-id; string user\-id;|} headers; -|}; +# Represents the Queries record for the operation: getAlbums +public type GetAlbumsQueries record { + string genre; +}; # Proxy server configurations to be used with the HTTP client endpoint. public type ProxyConfig record {| @@ -72,17 +46,48 @@ public type ProxyConfig record {| string password = ""; |}; -public type ErrorMessage record {| +public type NotFoundErrorMessage record {| + *http:NotFound; + ErrorMessage body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type ErrorPayload record { + string timestamp; + int status; + string reason; string message; - string...; + string path; + string method; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; |}; -public type NotFoundErrorMessage record {| - *http:NotFound; +public type ConflictErrorMessage record {| + *http:Conflict; ErrorMessage body; record {|int req\-id; string user\-id;|} headers; |}; +public type OkAlbum record {| + *http:Ok; + Album body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type ErrorMessage record {| + string message; + string...; +|}; + # Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. @display {label: "Connection Config"} public type ConnectionConfig record {| diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/types_all.bal b/openapi-integration-tests/src/test/resources/client/project-expected/types_all.bal new file mode 100644 index 000000000..c95b507a4 --- /dev/null +++ b/openapi-integration-tests/src/test/resources/client/project-expected/types_all.bal @@ -0,0 +1,122 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/http; + +public type CreatedAlbum record {| + *http:Created; + Album body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type BadRequestErrorPayload record {| + *http:BadRequest; + ErrorPayload body; + map headers; +|}; + +public type OkAlbumArray record {| + *http:Ok; + Album[] body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type Album record {| + string id; + string name; + string artist; + string genre; +|}; + +# Represents the Queries record for the operation: getAlbums +public type GetAlbumsQueries record { + string genre; +}; + +# Proxy server configurations to be used with the HTTP client endpoint. +public type ProxyConfig record {| + # Host name of the proxy server + string host = ""; + # Proxy server port + int port = 0; + # Proxy server username + string userName = ""; + # Proxy server password + @display {label: "", kind: "password"} + string password = ""; +|}; + +public type NotFoundErrorMessage record {| + *http:NotFound; + ErrorMessage body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type ErrorPayload record { + string timestamp; + int status; + string reason; + string message; + string path; + string method; +}; + +# Provides settings related to HTTP/1.x protocol. +public type ClientHttp1Settings record {| + # Specifies whether to reuse a connection for multiple requests + http:KeepAlive keepAlive = http:KEEPALIVE_AUTO; + # The chunking behaviour of the request + http:Chunking chunking = http:CHUNKING_AUTO; + # Proxy server related options + ProxyConfig proxy?; +|}; + +public type ConflictErrorMessage record {| + *http:Conflict; + ErrorMessage body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type OkAlbum record {| + *http:Ok; + Album body; + record {|int req\-id; string user\-id;|} headers; +|}; + +public type ErrorMessage record {| + string message; + string...; +|}; + +# Provides a set of configurations for controlling the behaviours when communicating with a remote HTTP endpoint. +@display {label: "Connection Config"} +public type ConnectionConfig record {| + # The HTTP version understood by the client + http:HttpVersion httpVersion = http:HTTP_2_0; + # Configurations related to HTTP/1.x protocol + ClientHttp1Settings http1Settings?; + # Configurations related to HTTP/2 protocol + http:ClientHttp2Settings http2Settings?; + # The maximum time to wait (in seconds) for a response before closing the connection + decimal timeout = 60; + # The choice of setting `forwarded`/`x-forwarded` header + string forwarded = "disable"; + # Configurations associated with request pooling + http:PoolConfiguration poolConfig?; + # HTTP caching related configurations + http:CacheConfig cache?; + # Specifies the way of handling compression (`accept-encoding`) header + http:Compression compression = http:COMPRESSION_AUTO; + # Configurations associated with the behaviour of the Circuit Breaker + http:CircuitBreakerConfig circuitBreaker?; + # Configurations associated with retrying + http:RetryConfig retryConfig?; + # Configurations associated with inbound response size limits + http:ResponseLimitConfigs responseLimits?; + # SSL/TLS-related options + http:ClientSecureSocket secureSocket?; + # Proxy server related options + http:ProxyConfig proxy?; + # Enables the inbound payload validation functionality which provided by the constraint package. Enabled by default + boolean validation = true; +|}; diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/utils.bal b/openapi-integration-tests/src/test/resources/client/project-expected/utils.bal index c8e31de56..8fb8e44f8 100644 --- a/openapi-integration-tests/src/test/resources/client/project-expected/utils.bal +++ b/openapi-integration-tests/src/test/resources/client/project-expected/utils.bal @@ -58,11 +58,11 @@ isolated function getFormStyleRequest(string parent, record {} anyRecord, boolea string[] recordArray = []; if explode { foreach [string, anydata] [key, value] in anyRecord.entries() { - if (value is SimpleBasicType) { + if value is SimpleBasicType { recordArray.push(key, "=", getEncodedUri(value.toString())); - } else if (value is SimpleBasicType[]) { + } else if value is SimpleBasicType[] { recordArray.push(getSerializedArray(key, value, explode = explode)); - } else if (value is record {}) { + } else if value is record {} { recordArray.push(getFormStyleRequest(parent, value, explode)); } recordArray.push("&"); @@ -70,11 +70,11 @@ isolated function getFormStyleRequest(string parent, record {} anyRecord, boolea _ = recordArray.pop(); } else { foreach [string, anydata] [key, value] in anyRecord.entries() { - if (value is SimpleBasicType) { + if value is SimpleBasicType { recordArray.push(key, ",", getEncodedUri(value.toString())); - } else if (value is SimpleBasicType[]) { + } else if value is SimpleBasicType[] { recordArray.push(getSerializedArray(key, value, explode = false)); - } else if (value is record {}) { + } else if value is record {} { recordArray.push(getFormStyleRequest(parent, value, explode)); } recordArray.push(","); @@ -94,23 +94,23 @@ isolated function getFormStyleRequest(string parent, record {} anyRecord, boolea isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { string key = arrayName; string[] arrayValues = []; - if (anyArray.length() > 0) { - if (style == FORM && !explode) { + if anyArray.length() > 0 { + if style == FORM && !explode { arrayValues.push(key, "="); foreach anydata i in anyArray { arrayValues.push(getEncodedUri(i.toString()), ","); } - } else if (style == SPACEDELIMITED && !explode) { + } else if style == SPACEDELIMITED && !explode { arrayValues.push(key, "="); foreach anydata i in anyArray { arrayValues.push(getEncodedUri(i.toString()), "%20"); } - } else if (style == PIPEDELIMITED && !explode) { + } else if style == PIPEDELIMITED && !explode { arrayValues.push(key, "="); foreach anydata i in anyArray { arrayValues.push(getEncodedUri(i.toString()), "|"); } - } else if (style == DEEPOBJECT) { + } else if style == DEEPOBJECT { foreach anydata i in anyArray { arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); } @@ -140,7 +140,7 @@ isolated function getSerializedRecordArray(string parent, record {}[] value, str arayIndex = arayIndex + 1; } } else { - if (!explode) { + if !explode { serializedArray.push(parent, "="); } foreach var recordItem in value { @@ -157,7 +157,7 @@ isolated function getSerializedRecordArray(string parent, record {}[] value, str # + return - Encoded string isolated function getEncodedUri(anydata value) returns string { string|error encoded = url:encode(value.toString(), "UTF8"); - if (encoded is string) { + if encoded is string { return encoded; } else { return value.toString(); @@ -171,7 +171,7 @@ isolated function getEncodedUri(anydata value) returns string { # + return - Returns generated Path or error at failure of client initialization isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { string[] param = []; - if (queryParam.length() > 0) { + if queryParam.length() > 0 { param.push("?"); foreach var [key, value] in queryParam.entries() { if value is () { @@ -179,12 +179,12 @@ isolated function getPathForQueryParam(map queryParam, map en continue; } Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; - if (value is SimpleBasicType) { + if value is SimpleBasicType { param.push(key, "=", getEncodedUri(value.toString())); - } else if (value is SimpleBasicType[]) { + } else if value is SimpleBasicType[] { param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); - } else if (value is record {}) { - if (encodingData.style == DEEPOBJECT) { + } else if value is record {} { + if encodingData.style == DEEPOBJECT { param.push(getDeepObjectStyleRequest(key, value)); } else { param.push(getFormStyleRequest(key, value, encodingData.explode)); diff --git a/openapi-integration-tests/src/test/resources/client/project-expected/utils_all.bal b/openapi-integration-tests/src/test/resources/client/project-expected/utils_all.bal new file mode 100644 index 000000000..461d33b75 --- /dev/null +++ b/openapi-integration-tests/src/test/resources/client/project-expected/utils_all.bal @@ -0,0 +1,201 @@ +// AUTO-GENERATED FILE. +// This file is auto-generated by the Ballerina OpenAPI tool. + +import ballerina/url; + +type SimpleBasicType string|boolean|int|float|decimal; + +# Represents encoding mechanism details. +type Encoding record { + # Defines how multiple values are delimited + string style = FORM; + # Specifies whether arrays and objects should generate as separate fields + boolean explode = true; + # Specifies the custom content type + string contentType?; + # Specifies the custom headers + map headers?; +}; + +enum EncodingStyle { + DEEPOBJECT, FORM, SPACEDELIMITED, PIPEDELIMITED +} + +final Encoding & readonly defaultEncoding = {}; + +# Serialize the record according to the deepObject style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + return - Serialized record as a string +isolated function getDeepObjectStyleRequest(string parent, record {} anyRecord) returns string { + string[] recordArray = []; + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(parent + "[" + key + "]" + "=" + getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(parent + "[" + key + "]" + "[]", value, DEEPOBJECT, true)); + } else if value is record {} { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getDeepObjectStyleRequest(nextParent, value)); + } else if value is record {}[] { + string nextParent = parent + "[" + key + "]"; + recordArray.push(getSerializedRecordArray(nextParent, value, DEEPOBJECT)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + return string:'join("", ...recordArray); +} + +# Serialize the record according to the form style. +# +# + parent - Parent record name +# + anyRecord - Record to be serialized +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getFormStyleRequest(string parent, record {} anyRecord, boolean explode = true) returns string { + string[] recordArray = []; + if explode { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = explode)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push("&"); + } + _ = recordArray.pop(); + } else { + foreach [string, anydata] [key, value] in anyRecord.entries() { + if value is SimpleBasicType { + recordArray.push(key, ",", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + recordArray.push(getSerializedArray(key, value, explode = false)); + } else if value is record {} { + recordArray.push(getFormStyleRequest(parent, value, explode)); + } + recordArray.push(","); + } + _ = recordArray.pop(); + } + return string:'join("", ...recordArray); +} + +# Serialize arrays. +# +# + arrayName - Name of the field with arrays +# + anyArray - Array to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized array as a string +isolated function getSerializedArray(string arrayName, anydata[] anyArray, string style = "form", boolean explode = true) returns string { + string key = arrayName; + string[] arrayValues = []; + if anyArray.length() > 0 { + if style == FORM && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), ","); + } + } else if style == SPACEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "%20"); + } + } else if style == PIPEDELIMITED && !explode { + arrayValues.push(key, "="); + foreach anydata i in anyArray { + arrayValues.push(getEncodedUri(i.toString()), "|"); + } + } else if style == DEEPOBJECT { + foreach anydata i in anyArray { + arrayValues.push(key, "[]", "=", getEncodedUri(i.toString()), "&"); + } + } else { + foreach anydata i in anyArray { + arrayValues.push(key, "=", getEncodedUri(i.toString()), "&"); + } + } + _ = arrayValues.pop(); + } + return string:'join("", ...arrayValues); +} + +# Serialize the array of records according to the form style. +# +# + parent - Parent record name +# + value - Array of records to be serialized +# + style - Defines how multiple values are delimited +# + explode - Specifies whether arrays and objects should generate separate parameters +# + return - Serialized record as a string +isolated function getSerializedRecordArray(string parent, record {}[] value, string style = FORM, boolean explode = true) returns string { + string[] serializedArray = []; + if style == DEEPOBJECT { + int arayIndex = 0; + foreach var recordItem in value { + serializedArray.push(getDeepObjectStyleRequest(parent + "[" + arayIndex.toString() + "]", recordItem), "&"); + arayIndex = arayIndex + 1; + } + } else { + if !explode { + serializedArray.push(parent, "="); + } + foreach var recordItem in value { + serializedArray.push(getFormStyleRequest(parent, recordItem, explode), ","); + } + } + _ = serializedArray.pop(); + return string:'join("", ...serializedArray); +} + +# Get Encoded URI for a given value. +# +# + value - Value to be encoded +# + return - Encoded string +isolated function getEncodedUri(anydata value) returns string { + string|error encoded = url:encode(value.toString(), "UTF8"); + if encoded is string { + return encoded; + } else { + return value.toString(); + } +} + +# Generate query path with query parameter. +# +# + queryParam - Query parameter map +# + encodingMap - Details on serialization mechanism +# + return - Returns generated Path or error at failure of client initialization +isolated function getPathForQueryParam(map queryParam, map encodingMap = {}) returns string|error { + string[] param = []; + if queryParam.length() > 0 { + param.push("?"); + foreach var [key, value] in queryParam.entries() { + if value is () { + _ = queryParam.remove(key); + continue; + } + Encoding encodingData = encodingMap.hasKey(key) ? encodingMap.get(key) : defaultEncoding; + if value is SimpleBasicType { + param.push(key, "=", getEncodedUri(value.toString())); + } else if value is SimpleBasicType[] { + param.push(getSerializedArray(key, value, encodingData.style, encodingData.explode)); + } else if value is record {} { + if encodingData.style == DEEPOBJECT { + param.push(getDeepObjectStyleRequest(key, value)); + } else { + param.push(getFormStyleRequest(key, value, encodingData.explode)); + } + } else { + param.push(key, "=", value.toString()); + } + param.push("&"); + } + _ = param.pop(); + } + string restOfPath = string:'join("", ...param); + return restOfPath; +}