Skip to content

Commit

Permalink
Merge branch 'master' into flattern-enable
Browse files Browse the repository at this point in the history
  • Loading branch information
lnash94 authored Aug 7, 2024
2 parents 94b9497 + fdce2fd commit 2614c93
Show file tree
Hide file tree
Showing 24 changed files with 553 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-ballerina-to-openapi.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Build
name: Build Ballerina to OpenAPI package
on:
workflow_dispatch:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
*/
package io.ballerina.openapi.service.mapper;

import io.ballerina.compiler.api.ModuleID;
import io.ballerina.compiler.api.SemanticModel;
import io.ballerina.compiler.api.symbols.ModuleSymbol;
import io.ballerina.compiler.api.symbols.ServiceDeclarationSymbol;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode;
Expand Down Expand Up @@ -95,6 +97,24 @@ public static List<OASResult> generateOAS3Definition(Project project, SyntaxTree
SemanticModel semanticModel,
String serviceName, Boolean needJson,
Path inputPath) {
return generateOAS3Definition(project, syntaxTree, semanticModel, serviceName, needJson, inputPath, false);
}

/**
* This method will generate openapi definition Map lists with ballerina code.
*
* @param syntaxTree - Syntax tree the related to ballerina service
* @param semanticModel - Semantic model related to ballerina module
* @param serviceName - Service name that need to generate the openAPI specification
* @param needJson - Flag for enabling the generated file format with json or YAML
* @param inputPath - Input file path for resolve the annotation details
* @param ballerinaExtension - Flag to enable ballerina type extension
* @return - {@link java.util.Map} with openAPI definitions for service nodes
*/
public static List<OASResult> generateOAS3Definition(Project project, SyntaxTree syntaxTree,
SemanticModel semanticModel,
String serviceName, Boolean needJson,
Path inputPath, Boolean ballerinaExtension) {
Map<String, ServiceNode> servicesToGenerate = new HashMap<>();
List<String> availableService = new ArrayList<>();
List<OpenAPIMapperDiagnostic> diagnostics = new ArrayList<>();
Expand All @@ -115,7 +135,7 @@ public static List<OASResult> generateOAS3Definition(Project project, SyntaxTree
for (Map.Entry<String, ServiceNode> serviceNode : servicesToGenerate.entrySet()) {
String openApiName = getOpenApiFileName(syntaxTree.filePath(), serviceNode.getKey(), needJson);
OASResult oasDefinition = generateOasFroServiceNode(project, openApiName,
semanticModel, inputPath, serviceNode.getValue());
semanticModel, inputPath, serviceNode.getValue(), ballerinaExtension);
outputs.add(oasDefinition);
}
}
Expand All @@ -127,13 +147,15 @@ public static List<OASResult> generateOAS3Definition(Project project, SyntaxTree
}

public static OASResult generateOasFroServiceNode(Project project, String openApiName, SemanticModel semanticModel,
Path inputPath, ServiceNode serviceNode) {
Path inputPath, ServiceNode serviceNode,
Boolean ballerinaExtension) {
OASGenerationMetaInfo.OASGenerationMetaInfoBuilder builder =
new OASGenerationMetaInfo.OASGenerationMetaInfoBuilder();
builder.setServiceNode(serviceNode)
.setSemanticModel(semanticModel)
.setOpenApiFileName(openApiName)
.setBallerinaFilePath(inputPath)
.setBallerinaExtension(ballerinaExtension)
.setProject(project);
OASGenerationMetaInfo oasGenerationMetaInfo = builder.build();
OASResult oasDefinition = generateOAS(oasGenerationMetaInfo);
Expand Down Expand Up @@ -275,7 +297,12 @@ public static OASResult generateOAS(OASGenerationMetaInfo oasGenerationMetaInfo)
openapi.setComponents(null);
}
// Remove ballerina extensions
BallerinaTypeExtensioner.removeExtensions(openapi);
Optional<ModuleID> serviceModuleId = getServiceModuleId(serviceDefinition, semanticModel);
if (oasGenerationMetaInfo.getBallerinaExtension() && serviceModuleId.isPresent()) {
BallerinaTypeExtensioner.removeCurrentModuleTypeExtensions(openapi, serviceModuleId.get());
} else {
BallerinaTypeExtensioner.removeExtensions(openapi);
}
return new OASResult(openapi, diagnostics);
} else {
return new OASResult(openapi, oasResult.getDiagnostics());
Expand Down Expand Up @@ -331,6 +358,11 @@ private static String extractBasePath(OpenAPI openApiFromServiceContract) {
return servers.get(0).getUrl().split("\\{server}:\\{port}").length == 2 ? parts[1] : "";
}

private static Optional<ModuleID> getServiceModuleId(ServiceNode serviceNode, SemanticModel semanticModel) {
return semanticModel.symbol(serviceNode.getInternalNode())
.flatMap(symbol -> symbol.getModule().map(ModuleSymbol::id));
}

/**
* Travers every syntax tree and collect all the listener nodes.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ private Optional<TypeDefinitionSymbol> getTypeDefinitionNode(String name, Schema
Optional<Symbol> ballerinaType;
if (ballerinaExt.isPresent()) {
BallerinaPackage ballerinaPkg = ballerinaExt.get();
String typeName = ballerinaPkg.name().orElse(name);
String typeName = Objects.isNull(ballerinaPkg.name()) ? name : ballerinaPkg.name();
ballerinaType = semanticModel.types().getTypeByName(ballerinaPkg.orgName(), ballerinaPkg.moduleName(),
ballerinaPkg.version(), typeName);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ public class OASGenerationMetaInfo {
private final SemanticModel semanticModel;
private final ServiceNode serviceNode;
private final Project project;
private final Boolean ballerinaExtensionLevel;

public OASGenerationMetaInfo(OASGenerationMetaInfoBuilder builder) {
this.openApiFileName = builder.openApiFileName;
Expand All @@ -47,6 +48,7 @@ public OASGenerationMetaInfo(OASGenerationMetaInfoBuilder builder) {
}
this.serviceNode = serviceNodeFromBuilder;
this.project = builder.project;
this.ballerinaExtensionLevel = builder.ballerinaExtension;
}

public String getOpenApiFileName() {
Expand All @@ -69,6 +71,10 @@ public Project getProject() {
return project;
}

public Boolean getBallerinaExtension() {
return ballerinaExtensionLevel;
}

/**
* This method is used to create a new {@link OASGenerationMetaInfoBuilder} instance.
*/
Expand All @@ -80,6 +86,7 @@ public static class OASGenerationMetaInfoBuilder {
private ServiceDeclarationNode serviceDeclarationNode;
private ServiceNode serviceNode;
private Project project;
private Boolean ballerinaExtension = false;

public OASGenerationMetaInfoBuilder setBallerinaFilePath(Path ballerinaFilePath) {
this.ballerinaFilePath = ballerinaFilePath;
Expand Down Expand Up @@ -111,6 +118,11 @@ public OASGenerationMetaInfoBuilder setOpenApiFileName(String openApiFileName) {
return this;
}

public OASGenerationMetaInfoBuilder setBallerinaExtension(Boolean balExt) {
this.ballerinaExtension = balExt;
return this;
}

public void setProject(Project project) {
this.project = project;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ private Optional<String> getOpenAPISpecFromResources(Package pkg, SemanticModel
return Optional.empty();
}
OASResult oasResult = generateOasFroServiceNode(pkg.project(), serviceName.get(), semanticModel, null,
serviceContract.get());
serviceContract.get(), false);
if (oasResult.getDiagnostics().stream()
.anyMatch(diagnostic -> diagnostic.getDiagnosticSeverity().equals(DiagnosticSeverity.ERROR))) {
diagnostics.add(new ExceptionDiagnostic(DiagnosticMessages.OAS_CONVERTOR_136, serviceName.get()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
*/
package io.ballerina.openapi.service.mapper.type.extension;

import java.util.Optional;

/**
* This {@link BallerinaPackage} record represents the Ballerina package details.
* @param orgName organization name
Expand All @@ -30,5 +28,5 @@
* @since 2.1.0
*/
public record BallerinaPackage(String orgName, String pkgName, String moduleName, String version,
String modulePrefix, Optional<String> name) {
String modulePrefix, String name) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,18 @@ public static void addExtension(Schema schema, TypeSymbol typeSymbol) {
}

public static void removeExtensions(OpenAPI openAPI) {
removeExtensionsFromSchemas(openAPI, (extensions, orgName, moduleName) -> true);
}

public static void removeCurrentModuleTypeExtensions(OpenAPI openAPI, ModuleID moduleID) {
String orgName = moduleID.orgName();
String moduleName = moduleID.moduleName();

removeExtensionsFromSchemas(openAPI, (extensions, org, mod) -> fromSameModule(extensions, orgName,
moduleName));
}

private static void removeExtensionsFromSchemas(OpenAPI openAPI, ExtensionRemovalCondition condition) {
Components components = openAPI.getComponents();
if (Objects.isNull(components)) {
return;
Expand All @@ -58,12 +70,22 @@ public static void removeExtensions(OpenAPI openAPI) {

schemas.forEach((key, schema) -> {
Map<?, ?> extensions = schema.getExtensions();
if (Objects.nonNull(extensions)) {
if (Objects.nonNull(extensions) && condition.shouldRemove(extensions, null, null)) {
extensions.remove(X_BALLERINA_TYPE);
}
});
}

@FunctionalInterface
private interface ExtensionRemovalCondition {
boolean shouldRemove(Map<?, ?> extensions, String orgName, String moduleName);
}

private static boolean fromSameModule(Map<?, ?> extensions, String orgName, String moduleName) {
return extensions.get(X_BALLERINA_TYPE) instanceof BallerinaPackage ballerinaPkg &&
orgName.equals(ballerinaPkg.orgName()) && moduleName.equals(ballerinaPkg.moduleName());
}

public static Optional<BallerinaPackage> getExtension(Schema schema) {
Map<?, ?> extensions = schema.getExtensions();
if (Objects.isNull(extensions)) {
Expand All @@ -84,6 +106,6 @@ static Optional<BallerinaPackage> getBallerinaPackage(TypeSymbol typeSymbol) {
}
ModuleID moduleID = module.get().id();
return Optional.of(new BallerinaPackage(moduleID.orgName(), moduleID.packageName(), moduleID.moduleName(),
moduleID.version(), moduleID.modulePrefix(), typeSymbol.getName()));
moduleID.version(), moduleID.modulePrefix(), typeSymbol.getName().orElse(null)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ private NodeList<DocumentMemberDeclarationNode> populateClientNativeDependency(
tomlMembers = tomlMembers.add(SampleNodeGenerator.createStringKV("groupId", "io.ballerina.openapi", null));
tomlMembers = tomlMembers.add(SampleNodeGenerator.createStringKV("artifactId", "client-native", null));
tomlMembers = tomlMembers.add(SampleNodeGenerator.createStringKV("version", version, null));
tomlMembers = tomlMembers.add(SampleNodeGenerator.createBooleanKV("graalvmCompatible", true, null));
return tomlMembers;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,7 @@ public class BaseCmd {
@CommandLine.Option(names = {"--status-code-binding"}, description = "Generate the client methods with " +
"status code response binding")
public boolean statusCodeBinding;



@CommandLine.Option(names = {"--mock"}, hidden = true,
description = "Generate mock client with given response example")
public boolean mock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName;
Expand All @@ -59,6 +60,7 @@ public class OASContractGenerator {
private Project project;
private List<OpenAPIMapperDiagnostic> diagnostics = new ArrayList<>();
private PrintStream outStream = System.out;
private Boolean ballerinaExtension = false;

/**
* Initialize constructor.
Expand All @@ -67,6 +69,12 @@ public OASContractGenerator() {

}

public void setBallerinaExtension(Boolean ballerinaExtension) {
if (Objects.nonNull(ballerinaExtension)) {
this.ballerinaExtension = ballerinaExtension;
}
}

public List<OpenAPIMapperDiagnostic> getDiagnostics() {
return diagnostics;
}
Expand Down Expand Up @@ -118,7 +126,7 @@ public void generateOAS3DefinitionsAllService(Path servicePath, Path outPath, St
}
semanticModel = compilation.getSemanticModel(docId.moduleId());
List<OASResult> openAPIDefinitions = ServiceToOpenAPIMapper.generateOAS3Definition(project, syntaxTree,
semanticModel, serviceName, needJson, inputPath);
semanticModel, serviceName, needJson, inputPath, ballerinaExtension);

if (!openAPIDefinitions.isEmpty()) {
List<String> fileNames = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ public class OpenApiCmd implements BLauncherCmd {
description = "Generate service without data binding")
private boolean generateWithoutDataBinding;

@CommandLine.Option(names = {"--with-bal-ext"}, hidden = true, description = "Generate ballerina type extensions")
private boolean addBallerinaExtension;


@CommandLine.Parameters
private List<String> argList;
Expand Down Expand Up @@ -227,7 +230,7 @@ public void execute() {

if (baseCmd.statusCodeBinding) {
if (baseCmd.mode != null && baseCmd.mode.equals(SERVICE)) {
outStream.println("ERROR: the '--status-code-binding' option is only available in client " +
outStream.println("the '--status-code-binding' option is only available in client " +
"generation mode.");
exitError(this.exitWhenFinish);
}
Expand All @@ -242,6 +245,12 @@ public void execute() {
}
}

if (addBallerinaExtension) {
outStream.println("'--with-bal-ext' option is only available in OpenAPI specification " +
"generation mode.");
exitError(this.exitWhenFinish);
}

try {
openApiToBallerina(fileName, filter);
} catch (IOException e) {
Expand Down Expand Up @@ -293,6 +302,7 @@ private void ballerinaToOpenApi(String fileName) {
getTargetOutputPath();
// Check service name it is mandatory
OASContractGenerator openApiConverter = new OASContractGenerator();
openApiConverter.setBallerinaExtension(addBallerinaExtension);
openApiConverter.generateOAS3DefinitionsAllService(balFilePath, targetOutputPath, service,
generatedFileType);
mapperDiagnostics.addAll(openApiConverter.getDiagnostics());
Expand Down Expand Up @@ -558,6 +568,7 @@ private NodeList<DocumentMemberDeclarationNode> populateClientNativeDependency(
tomlMembers = tomlMembers.add(SampleNodeGenerator.createStringKV("groupId", "io.ballerina.openapi", null));
tomlMembers = tomlMembers.add(SampleNodeGenerator.createStringKV("artifactId", "client-native", null));
tomlMembers = tomlMembers.add(SampleNodeGenerator.createStringKV("version", version, null));
tomlMembers = tomlMembers.add(SampleNodeGenerator.createBooleanKV("graalvmCompatible", true, null));
return tomlMembers;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,48 @@ public void testOneOfSchemaGen() throws IOException {
}
}

@Test(description = "Test successful service generation from OpenAPI which contains an array schema with " +
"multiple members")
public void testArrayOfOneOfSchemaGen() throws IOException {
Path yamlPath = resourceDir.resolve(Paths.get("array-of-oneof.yaml"));
String[] args = {"--input", yamlPath.toString(), "-o", this.tmpDir.toString(), "--mode", "service"};
OpenApiCmd cmd = new OpenApiCmd(printStream, this.tmpDir);
new CommandLine(cmd).parseArgs(args);
try {
cmd.execute();
} catch (BLauncherException e) {
String errorMsg = String.format("Service generation with array of OneOf Schema type failed: %s",
e.getDetailedMessages().get(0));
Assert.fail(errorMsg);
}

Path expectedServiceFile = resourceDir.resolve(Paths.get("expected_gen",
"array-of-oneof-schema.bal"));
String expectedService = new String(Files.readAllBytes(expectedServiceFile));
if (Files.exists(this.tmpDir.resolve("array-of-oneof_service.bal"))) {
String generatedService = getStringFromFile(this.tmpDir.resolve("array-of-oneof_service.bal"));
Assert.assertEquals(replaceWhiteSpace(generatedService.replaceAll(replaceRegex, "")),
replaceWhiteSpace(expectedService.replaceAll(replaceRegex, "")),
"Expected content and actual generated content is mismatched for: " + yamlPath);
deleteGeneratedFiles("array-of-oneof_service.bal");
} else {
Assert.fail("Service generation with array of OneOf Schema type failed.");
}
}

@Test(description = "Test for --without-data-binding flag")
public void testWithoutDataBinding() throws IOException {
Path yamlPath = resourceDir.resolve(Paths.get("withoutDataBinding.yaml"));
String[] args = {"--input", yamlPath.toString(), "--without-data-binding", "-o",
this.tmpDir.toString(), "--mode", "service"};
OpenApiCmd cmd = new OpenApiCmd(printStream, this.tmpDir);
new CommandLine(cmd).parseArgs(args);
String output = "";
try {
cmd.execute();
} catch (BLauncherException e) {
output = e.getDetailedMessages().get(0);
String errorMsg = String.format("Service generation for low level service is failed: %s",
e.getDetailedMessages().get(0));
Assert.fail(errorMsg);
}

Path expectedServiceFile = resourceDir.resolve(Paths.get("expected_gen",
Expand Down
Loading

0 comments on commit 2614c93

Please sign in to comment.