Skip to content

Commit

Permalink
Merge pull request #1686 from ballerina-platform/status-code-binding
Browse files Browse the repository at this point in the history
Address the the client external function call changes from runtime
  • Loading branch information
TharmiganK committed Apr 18, 2024
2 parents 3cd3c43 + c35fe2a commit 9a05b7d
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 38 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version=1.9.0-SNAPSHOT
clientNativeVersion=1.0.0-SNAPSHOT

#dependency
ballerinaLangVersion=2201.9.0-20240410-095500-2653a74d
ballerinaLangVersion=2201.9.0-20240418-134100-a0610c90
testngVersion=7.6.1
slf4jVersion=1.7.30
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ public enum DiagnosticMessages {
" updated with the OpenAPI client native dependency but the version is different from the current " +
"version. Please remove the existing dependency and try again.", DiagnosticSeverity.ERROR),
ERROR_WHILE_UPDATING_TOML("OAS_CLIENT_11", "error occurred when updating Ballerina.toml " +
"file with the client native dependency.", DiagnosticSeverity.ERROR),
WARNING_FOR_UNSUPPORTED_MEDIA_TYPE("OAS_CLIENT_12", "the generated client will not have status code" +
" response binding since the OpenAPI definition contains unsupported media-type for request " +
"payload binding.", DiagnosticSeverity.WARNING);
"file with the client native dependency.", DiagnosticSeverity.ERROR);

private final String code;
private final String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import io.ballerina.openapi.core.generators.client.BallerinaClientGeneratorWithStatusCodeBinding;
import io.ballerina.openapi.core.generators.client.exception.ClientException;
import io.ballerina.openapi.core.generators.client.model.OASClientConfig;
import io.ballerina.openapi.core.generators.common.GeneratorUtils;
import io.ballerina.openapi.core.generators.common.TypeHandler;
import io.ballerina.openapi.core.generators.common.exception.BallerinaOpenApiException;
import io.ballerina.openapi.core.generators.common.model.Filter;
Expand Down Expand Up @@ -454,8 +453,7 @@ private static List<GenSrcFile> generateClientFiles(OASClientConfig oasClientCon
// Generate ballerina client files.
TypeHandler.createInstance(oasClientConfig.getOpenAPI(), oasClientConfig.isNullable());
String licenseContent = oasClientConfig.getLicense();
BallerinaClientGenerator ballerinaClientGenerator = getClientGenerator(oasClientConfig, statusCodeBinding,
toolContext, location);
BallerinaClientGenerator ballerinaClientGenerator = getClientGenerator(oasClientConfig, statusCodeBinding);
String mainContent = Formatter.format(ballerinaClientGenerator.generateSyntaxTree()).toString();
sourceFiles.add(new GenSrcFile(GenSrcFile.GenFileType.GEN_SRC, null, CLIENT_FILE_NAME,
licenseContent == null || licenseContent.isBlank() ? mainContent :
Expand Down Expand Up @@ -489,25 +487,13 @@ private static List<GenSrcFile> generateClientFiles(OASClientConfig oasClientCon
}

private static BallerinaClientGenerator getClientGenerator(OASClientConfig oasClientConfig,
boolean statusCodeBinding, ToolContext toolContext,
Location location) {
if (!statusCodeBinding || hasRequestBinding(oasClientConfig.getOpenAPI())) {
if (statusCodeBinding) {
createDiagnostics(toolContext, DiagnosticMessages.WARNING_FOR_UNSUPPORTED_MEDIA_TYPE,
location);
}
boolean statusCodeBinding) {
if (!statusCodeBinding) {
return new BallerinaClientGenerator(oasClientConfig);
}
return new BallerinaClientGeneratorWithStatusCodeBinding(oasClientConfig);
}

private static boolean hasRequestBinding(OpenAPI openAPI) {
return openAPI.getPaths().values().stream().anyMatch(pathItem -> pathItem.readOperations().stream()
.anyMatch(operation -> operation.getRequestBody() != null &&
operation.getRequestBody().getContent().keySet().stream()
.anyMatch(GeneratorUtils::hasRequestBinding)));
}

/**
* Util to read license content.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,6 @@ public void generateClientAndService(String definitionPath, String serviceName,
List<ClientDiagnostic> clientDiagnostic = clientGenerator.getDiagnostics();

if (!clientDiagnostic.isEmpty()) {
outStream.println("error occurred while generating the client: ");
for (ClientDiagnostic diagnostic : clientDiagnostic) {
outStream.println(diagnostic.getDiagnosticSeverity() + ":" + diagnostic.getMessage());
}
Expand Down Expand Up @@ -434,7 +433,6 @@ private List<GenSrcFile> generateClientFiles(Path openAPI, Filter filter, boolea

List<ClientDiagnostic> clientDiagnostic = clientGenerator.getDiagnostics();
if (!clientDiagnostic.isEmpty()) {
outStream.println("error occurred while generating the client: ");
for (ClientDiagnostic diagnostic : clientDiagnostic) {
outStream.println(diagnostic.getDiagnosticSeverity() + ":" + diagnostic.getMessage());
}
Expand All @@ -444,23 +442,12 @@ private List<GenSrcFile> generateClientFiles(Path openAPI, Filter filter, boolea

private static BallerinaClientGenerator getBallerinaClientGenerator(OASClientConfig oasClientConfig,
boolean statusCodeBinding) {
if (!statusCodeBinding || hasRequestBinding(oasClientConfig.getOpenAPI())) {
if (statusCodeBinding) {
outStream.println("WARNING: the generated client will not have status code response binding since " +
"the OpenAPI definition contains unsupported media-type for request payload binding.");
}
if (!statusCodeBinding) {
return new BallerinaClientGenerator(oasClientConfig);
}
return new BallerinaClientGeneratorWithStatusCodeBinding(oasClientConfig);
}

private static boolean hasRequestBinding(OpenAPI openAPI) {
return openAPI.getPaths().values().stream().anyMatch(pathItem -> pathItem.readOperations().stream()
.anyMatch(operation -> operation.getRequestBody() != null &&
operation.getRequestBody().getContent().keySet().stream()
.anyMatch(GeneratorUtils::hasRequestBinding)));
}


public List<GenSrcFile> generateBallerinaService(Path openAPI, String serviceName,
Filter filter, boolean nullable, boolean generateServiceType,
Expand Down
58 changes: 58 additions & 0 deletions openapi-client-native/ballerina-tests/client.bal
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,69 @@ public isolated client class Client {
@MethodImpl {name: "getAlbumsAllImpl"}
resource isolated function get albums\-all(typedesc<Album[]|OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload> targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invokeResourceWithoutPath"} external;

@MethodImpl {name: "getAlbumsAllImpl"}
remote isolated function getAlbumsAll(typedesc<Album[]|OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload> targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external;

private isolated function getAlbumsAllImpl(typedesc<Album[]|OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload> targetType) returns Album[]|OkAlbumArray|NotFoundErrorMessage|BadRequestErrorPayload|error {
string resourcePath = string `/albums-all`;
return self.clientEp->get(resourcePath, targetType = targetType);
}

@MethodImpl {name: "postAlbumsImpl"}
resource isolated function post albums(http:Request req, typedesc<Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload> 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<Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload> targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external;

private isolated function postAlbumsImpl(http:Request req, typedesc<Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload> targetType) returns Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error {
string resourcePath = string `/albums`;
return self.clientEp->post(resourcePath, req, targetType = targetType);
}

@MethodImpl {name: "postAlbums1Impl"}
resource isolated function post albums\-1/[string a]/[int b](http:Request req, typedesc<Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload> 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<Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload> 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<Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload> targetType) returns Album|CreatedAlbum|ConflictAlbum|BadRequestErrorPayload|error {
string resourcePath = string `/albums`;
return self.clientEp->post(resourcePath, req, targetType = targetType);
}

@MethodImpl {name: "postAlbumsAllImpl"}
resource isolated function post albums\-all/[string a](Album[] albums, string? query = (), typedesc<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType = <>) 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<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType = <>) 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<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType) returns Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error {
string resourcePath = string `/albums-all`;
return self.clientEp->post(resourcePath, albums, targetType = targetType);
}

@MethodImpl {name: "postAlbumsAll1Impl"}
resource isolated function post albums\-all\-1(http:Request req, string? query = (), typedesc<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType = <>) 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<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external;

private isolated function postAlbumsAll1Impl(http:Request req, string? query, typedesc<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType) returns Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error {
string resourcePath = string `/albums-all`;
return self.clientEp->post(resourcePath, req, targetType = targetType);
}

@MethodImpl {name: "postAlbumsAll2Impl"}
resource isolated function post albums\-all\-2(Album[] albums, http:Request req, string? query = (), typedesc<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType = <>) 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<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType = <>) 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<Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload> targetType) returns Album|CreatedAlbumArray|ConflictAlbum|BadRequestErrorPayload|error {
string resourcePath = string `/albums-all`;
return self.clientEp->post(resourcePath, albums, targetType = targetType);
}

@MethodImpl {name: "getAlbumsIdImpl"}
remote isolated function getAlbumsId(string id, typedesc<Album|OkAlbum|NotFoundErrorMessage|BadRequestErrorPayload> targetType = <>) returns targetType|error = @java:Method {'class: "io.ballerina.openapi.client.GeneratedClient", name: "invoke"} external;

Expand Down
33 changes: 32 additions & 1 deletion openapi-client-native/ballerina-tests/service.bal
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ service /api on new http:Listener(9999) {
Album[] albumsArr = albums.toArray();
if albumsArr.length() == 0 {
return {
body: {"message": "No albums found"},
body: {message: "No albums found"},
headers: {user\-id: "user-1", req\-id: 1}
};
}
Expand All @@ -50,4 +50,35 @@ service /api on new http:Listener(9999) {
headers: {user\-id: "user-1", req\-id: 1}
};
}

resource function post albums(Album album) returns CreatedAlbum|ConflictAlbum {
if (albums.hasKey(album.id)) {
return {
body: {message: "Album already exists", "albumId": album.id},
headers: {user\-id: "user-1", req\-id: 1}
};
}
albums.add(album);
return {
body: album,
headers: {user\-id: "user-1", req\-id: 1}
};
}

resource function post albums\-all(Album[] albumArr, string? query = ()) returns CreatedAlbumArray|ConflictAlbum {
foreach Album album in albumArr {
if (albums.hasKey(album.id)) {
return {
body: {message: "Album already exists", "albumId": album.id},
headers: {user\-id: "user-1", req\-id: 1}
};
} else {
albums.add(album);
}
}
return {
body: albumArr,
headers: {user\-id: "user-1", req\-id: 1}
};
}
}
Loading

0 comments on commit 9a05b7d

Please sign in to comment.