Skip to content

Commit

Permalink
Merge pull request #1743 from lnash94/flattern-enable
Browse files Browse the repository at this point in the history
[WIP] Enhance generated record name sanitisation for OAS schema name
  • Loading branch information
lnash94 authored Aug 7, 2024
2 parents fdce2fd + 2614c93 commit 1bf8577
Show file tree
Hide file tree
Showing 66 changed files with 1,201 additions and 159 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ clientNativeVersion=1.0.1-SNAPSHOT
clientNativePublish=false

#dependency
ballerinaLangVersion=2201.10.0-20240801-104200-87df251c
ballerinaLangVersion=2201.10.0-20240806-083400-aabac46a
testngVersion=7.6.1
slf4jVersion=1.7.30
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
Expand Down
6 changes: 3 additions & 3 deletions module-ballerina-openapi/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
org= "ballerina"
name= "openapi"
version= "2.1.0"
version= "@toml.version@"

[[platform.java17.dependency]]
path = "../openapi-validator/build/libs/openapi-validator-2.1.0-SNAPSHOT.jar"
path = "../openapi-validator/build/libs/openapi-validator-@project.version@.jar"
groupId = "ballerina"
artifactId = "openapi"
version = "2.1.0-SNAPSHOT"
version = "@project.version@"
4 changes: 2 additions & 2 deletions module-ballerina-openapi/CompilerPlugin.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ id = "openapi-tools"
class = "io.ballerina.openapi.validator.OpenAPIValidatorPlugin"

[[dependency]]
path = "../openapi-validator/build/libs/openapi-validator-2.1.0-SNAPSHOT.jar"
path = "../openapi-validator/build/libs/openapi-validator-@project.version@.jar"
groupId = "ballerina"
artifactId = "openapi"
version = "2.1.0-SNAPSHOT"
version = "@project.version@."
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class Constants {
public static final String STATUS_CODE_BINDING = "statusCodeBinding";
public static final String MOCK = "mock";
public static final String SINGLE_FILE = "singleFile";
public static final String IS_SANITIZED_OAS = "isUsingSanitizedOas";

/**
* Enum class for containing diagnostic messages.
Expand Down Expand Up @@ -70,7 +71,10 @@ public enum DiagnosticMessages {
ERROR_WHILE_UPDATING_TOML("OAS_CLIENT_11", "error occurred when updating Ballerina.toml " +
"file with the client native dependency.", DiagnosticSeverity.ERROR),
OPENAPI_EXCEPTION("OAS_CLIENT_12", "exception occurred while reading the openapi contract: %s",
DiagnosticSeverity.ERROR);
DiagnosticSeverity.ERROR),
OPENAPI_MODIFICATION("OAS_CLIENT_13", "This `isUsingSanitizedOas` is an experimental feature." +
" This option enables code generation by " +
"modifying the given OAS to follow the Ballerina language best practices.", DiagnosticSeverity.WARNING);

private final String code;
private final String description;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@
import static io.ballerina.openapi.bal.tool.Constants.CACHE_FILE;
import static io.ballerina.openapi.bal.tool.Constants.CLIENT;
import static io.ballerina.openapi.bal.tool.Constants.CLIENT_METHODS;
import static io.ballerina.openapi.bal.tool.Constants.DiagnosticMessages.OPENAPI_MODIFICATION;
import static io.ballerina.openapi.bal.tool.Constants.IS_SANITIZED_OAS;
import static io.ballerina.openapi.bal.tool.Constants.LICENSE;
import static io.ballerina.openapi.bal.tool.Constants.MOCK;
import static io.ballerina.openapi.bal.tool.Constants.MODE;
Expand Down Expand Up @@ -121,13 +123,14 @@ public void execute(ToolContext toolContext) {
// Handle the code generation
String oasFilePath = toolContext.filePath();
Path packagePath = toolContext.currentPackage().project().sourceRoot();
Map<String, ToolContext.Option> options = toolContext.options();

Optional<OpenAPI> openAPI = getOpenAPIContract(packagePath, Path.of(oasFilePath), location, toolContext);
if (openAPI.isEmpty()) {
return;
}
//Extract the details using the `tool config options` table in the `Ballerina.toml` file.
//If the `tool options` table is not specified in the TOML file, the client will be generated by default.
Map<String, ToolContext.Option> options = toolContext.options();
if (options == null) {
// Default generate client
Filter filter = new Filter();
Expand Down Expand Up @@ -215,6 +218,14 @@ private boolean canHandle(ToolContext toolContext) {
private Optional<OpenAPI> getOpenAPIContract(Path ballerinaFilePath, Path openAPIPath, Location location,
ToolContext toolContext) {
Path relativePath;
boolean isSanitized = false;
Map<String, ToolContext.Option> options = toolContext.options();
if (options != null && options.containsKey(IS_SANITIZED_OAS)) {
ToolContext.Option isUsingSanitizedOas = options.get(IS_SANITIZED_OAS);
String value = isUsingSanitizedOas.value().toString().trim();
isSanitized = Boolean.parseBoolean(value);
createDiagnostics(toolContext, OPENAPI_MODIFICATION, location);
}
try {
Path inputPath = Paths.get(openAPIPath.toString());
if (inputPath.isAbsolute()) {
Expand All @@ -225,7 +236,8 @@ private Optional<OpenAPI> getOpenAPIContract(Path ballerinaFilePath, Path openAP
relativePath = Paths.get(openapiContract.getCanonicalPath());
}
if (Files.exists(relativePath)) {
return Optional.of(normalizeOpenAPI(relativePath, operationIdValidationRequired(toolContext)));
return Optional.of(normalizeOpenAPI(relativePath, operationIdValidationRequired(toolContext),
isSanitized));
} else {
DiagnosticMessages error = DiagnosticMessages.INVALID_CONTRACT_PATH;
createDiagnostics(toolContext, error, location);
Expand Down Expand Up @@ -307,6 +319,10 @@ public ImmutablePair<OASClientConfig, OASServiceMetadata> extractOptionDetails(T
case SINGLE_FILE:
clientMetaDataBuilder.withSingleFile(value.contains(TRUE));
break;
case IS_SANITIZED_OAS:
clientMetaDataBuilder.withIsUsingSanitizedOas(value.contains(TRUE));
serviceMetaDataBuilder.withIsUsingSanitizedOas(value.contains(TRUE));
break;
default:
break;
}
Expand Down Expand Up @@ -466,7 +482,8 @@ private String getHashValue(OASClientConfig clientConfig, String targetPath) {
.append(clientConfig.isNullable())
.append(clientConfig.isStatusCodeBinding())
.append(clientConfig.isMock())
.append(clientConfig.singleFile());
.append(clientConfig.singleFile())
.append(clientConfig.isUsingSanitizedOas());
List<String> tags = clientConfig.getFilter().getTags();
tags.sort(String.CASE_INSENSITIVE_ORDER);
for (String str : tags) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@
},
"singleFile": {
"type": "boolean"
},
"isUsingSanitizedOas": {
"type": "boolean"
}
},
"additionalProperties": false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public void generateClientAndService(String definitionPath, String serviceName,
// Normalize OpenAPI definition, in the client generation we suppose to terminate code generation when the
// absence of the operationId in operation. Therefor we enable client flag true as default code generation.
// if resource is enabled, we avoid checking operationId.
OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPIPath, !options.isResource);
OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPIPath, !options.isResource, options.isSanitizedOas);
checkOpenAPIVersion(openAPIDef);
// Add typeHandler
TypeHandler.createInstance(openAPIDef, options.nullable);
Expand Down Expand Up @@ -222,10 +222,12 @@ public void generateClientAndService(String definitionPath, String serviceName,
* service without data binding
* @param statusCodeBinding Enable statusCodeBinding option
* @param isMock Enable isMock option to generate mocks
* @param isSanitizedOas Enable isSanitizedOas option to modify the OAS to follow the Ballerina
* language best practices
*/
public record ClientServiceGeneratorOptions(boolean nullable, boolean isResource, boolean generateServiceType,
boolean generateServiceContract, boolean generateWithoutDataBinding,
boolean statusCodeBinding, boolean isMock) { }
boolean statusCodeBinding, boolean isMock, boolean isSanitizedOas) { }

public static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
Expand Down Expand Up @@ -295,10 +297,12 @@ public void generateService(String definitionPath, String serviceName, String ou
* @param generateServiceContract Enable to generate service contract
* @param generateWithoutDataBinding Enable to generate service without data binding
* @param singleFile Enable singleFile option to generate all content in a single file
* @param isSanitizedOas Enable isSanitizedOas option to modify the OAS to follow the Ballerina
* language best practices
*/
public record ServiceGeneratorOptions(boolean nullable, boolean generateServiceType,
boolean generateServiceContract, boolean generateWithoutDataBinding,
boolean singleFile) {
boolean singleFile, boolean isSanitizedOas) {
}

private void writeGeneratedSources(List<GenSrcFile> sources, Path srcPath, Path implPath,
Expand Down Expand Up @@ -391,7 +395,7 @@ private List<GenSrcFile> generateClientFiles(Path openAPI, Filter filter, Client
}
List<GenSrcFile> sourceFiles = new ArrayList<>();
// Normalize OpenAPI definition
OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, !options.isResource);
OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, !options.isResource, options.isSanitizedOas);
checkOpenAPIVersion(openAPIDef);
// Validate the service generation
List<String> complexPaths = GeneratorUtils.getComplexPaths(openAPIDef);
Expand Down Expand Up @@ -469,9 +473,11 @@ private List<GenSrcFile> generateClientFiles(Path openAPI, Filter filter, Client
* @param statusCodeBinding Enable statusCodeBinding option
* @param isMock Enable isMock option to generate mocks
* @param singleFile Enable singleFile option to generate all content in a single file
* @param isSanitizedOas Enable isSanitizedOas option to modify the OAS to follow the Ballerina
* language best practices
*/
public record ClientGeneratorOptions(boolean nullable, boolean isResource, boolean statusCodeBinding,
boolean isMock, boolean singleFile) { }
boolean isMock, boolean singleFile, boolean isSanitizedOas) { }

private void generateFilesForClient(SyntaxTree syntaxTree, List<GenSrcFile> sourceFiles,
BallerinaClientGenerator clientGenerator) throws FormatterException,
Expand Down Expand Up @@ -529,7 +535,7 @@ public List<GenSrcFile> generateBallerinaService(Path openAPI, String serviceNam
if (srcPackage == null || srcPackage.isEmpty()) {
srcPackage = DEFAULT_MOCK_PKG;
}
OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, false);
OpenAPI openAPIDef = GeneratorUtils.normalizeOpenAPI(openAPI, false, options.isSanitizedOas);
if (openAPIDef.getInfo() == null) {
throw new BallerinaOpenApiException("Info section of the definition file cannot be empty/null: " +
openAPI);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,9 @@ public class BaseCmd {

@CommandLine.Option(names = {"--single-file"}, description = "Generate all contents in a single file")
public boolean singleFile;

@CommandLine.Option(names = {"--use-sanitized-oas"}, hidden = true, description = "This is an experimental" +
" feature. This option enables code generation by modifying the given OAS to follow the" +
" Ballerina language best practices.")
public boolean useSanitized;
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ public void execute() {
}
Filter filter = new Filter(tag, operation);

if (baseCmd.useSanitized) {
outStream.println("This is an experimental feature. This option enables code generation by " +
"modifying the given OAS to follow the Ballerina language best practices.");
}
if (baseCmd.generateClientMethods != null && !baseCmd.generateClientMethods.isBlank() &&
(!baseCmd.generateClientMethods.equals(RESOURCE) &&
!baseCmd.generateClientMethods.equals(REMOTE))) {
Expand Down Expand Up @@ -433,7 +437,7 @@ private void generatesClientFile(BallerinaCodeGenerator generator, Path resource
try {
generator.generateClient(resourcePath.toString(), targetOutputPath.toString(), filter,
new BallerinaCodeGenerator.ClientGeneratorOptions(baseCmd.nullable, resourceMode,
statusCodeBinding, baseCmd.mock, baseCmd.singleFile));
statusCodeBinding, baseCmd.mock, baseCmd.singleFile, baseCmd.useSanitized));
} catch (IOException | FormatterException | BallerinaOpenApiException |
OASTypeGenException e) {
if (e.getLocalizedMessage() != null) {
Expand Down Expand Up @@ -464,7 +468,8 @@ private void generateServiceFile(BallerinaCodeGenerator generator, String servic
exitError(this.exitWhenFinish);
} else {
ServiceGeneratorOptions options = new ServiceGeneratorOptions(baseCmd.nullable, generateServiceType,
generateServiceContract, generateWithoutDataBinding, baseCmd.singleFile);
generateServiceContract, generateWithoutDataBinding, baseCmd.singleFile,
baseCmd.useSanitized);
generator.generateService(resourcePath.toString(), serviceName, targetOutputPath.toString(), filter,
options);
}
Expand All @@ -487,7 +492,7 @@ private void generateBothFiles(BallerinaCodeGenerator generator, String fileName
assert resourcePath != null;
ClientServiceGeneratorOptions options = new ClientServiceGeneratorOptions(baseCmd.nullable,
generateClientResourceFunctions, generateServiceType, generateServiceContract,
generateWithoutDataBinding, statusCodeBinding, baseCmd.mock);
generateWithoutDataBinding, statusCodeBinding, baseCmd.mock, baseCmd.useSanitized);
generator.generateClientAndService(resourcePath.toString(), fileName, targetOutputPath.toString(), filter,
options);
} catch (BallerinaOpenApiException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ public class CodeGeneratorTest {
List<String> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
Filter filter = new Filter(list1, list2);
ServiceGeneratorOptions defaultServiceOptions = new ServiceGeneratorOptions(false, false, false, false, false);
ClientGeneratorOptions defaultClientOptions = new ClientGeneratorOptions(false, false, false, false, false);
ServiceGeneratorOptions defaultServiceOptions = new ServiceGeneratorOptions(false, false,
false, false, false, false);
ClientGeneratorOptions defaultClientOptions = new ClientGeneratorOptions(false, false,
false, false, false, false);

String replaceRegex = System.getProperty("os.name").toLowerCase()
.contains("windows") ? "#.*[+*a\\r\\n]" : "#.*[+*a\\n]";
Expand Down Expand Up @@ -232,7 +234,8 @@ public void generateTypesWithNullableFields() {
try {
String expectedClientContent = getStringFromGivenBalFile(expectedDirPath, "nullable_types.bal");
generator.generateClient(definitionPath, resourcePath.toString(), filter,
new ClientGeneratorOptions(true, false, false, false, false));
new ClientGeneratorOptions(true, false, false, false,
false, false));

if (Files.exists(resourcePath.resolve("types.bal"))) {
String generatedClient = getStringFromGivenBalFile(resourcePath, "types.bal");
Expand All @@ -257,7 +260,8 @@ public void generateTypesWithNullableFieldsAndGlobalNullableTrue() {
try {
String expectedClientContent = getStringFromGivenBalFile(expectedDirPath, "nullable_false_types.bal");
generator.generateClient(definitionPath, resourcePath.toString(), filter,
new ClientGeneratorOptions(true, false, false, false, false));
new ClientGeneratorOptions(true, false, false, false,
false, false));

if (Files.exists(resourcePath.resolve("types.bal"))) {
String generatedClient = getStringFromGivenBalFile(resourcePath, "types.bal");
Expand All @@ -282,7 +286,8 @@ public void generateUtilsFile() {
try {
String expectedClientContent = getStringFromGivenBalFile(expectedDirPath, "utils.bal");
generator.generateClient(definitionPath, resourcePath.toString(), filter,
new ClientGeneratorOptions(true, false, false, false, false));
new ClientGeneratorOptions(true, false, false, false,
false, false));

if (Files.exists(resourcePath.resolve("utils.bal"))) {
String generatedClient = getStringFromGivenBalFile(resourcePath, "utils.bal");
Expand All @@ -308,7 +313,8 @@ public void generateConfigFile() {
String expectedConfigContent = getStringFromGivenBalFile(expectedDirPath, "api_key_config.toml");
generator.setIncludeTestFiles(true);
generator.generateClient(definitionPath, resourcePath.toString(), filter,
new ClientGeneratorOptions(true, false, false, false, false));
new ClientGeneratorOptions(true, false, false, false,
false, false));

if (Files.exists(resourcePath.resolve("tests/Config.toml"))) {
String generateConfigContent = getStringFromGivenBalFile(resourcePath, "tests/Config.toml");
Expand Down Expand Up @@ -448,7 +454,8 @@ public void generateClientForResourceWithCatchAllPath() {
String expectedClientContent = getStringFromGivenBalFile(
expectedDirPath, "petstore_catch_all_path_client.bal");
generator.generateClient(definitionPath, resourcePath.toString(), filter,
new ClientGeneratorOptions(false, true, false, false, false));
new ClientGeneratorOptions(false, true, false, false,
false, false));
if (Files.exists(resourcePath.resolve("client.bal"))) {
String generatedClient = getStringFromGivenBalFile(resourcePath, "client.bal");
generatedClient = (generatedClient.trim()).replaceAll("\\s+", "");
Expand Down Expand Up @@ -616,7 +623,8 @@ public void testGenericServiceGeneration() {
try {
String expectedServiceContent = getStringWithNewlineFromGivenBalFile(expectedDirPath,
"generic_service_petstore_original.bal");
ServiceGeneratorOptions options = new ServiceGeneratorOptions(false, false, false, true, false);
ServiceGeneratorOptions options = new ServiceGeneratorOptions(false, false,
false, true, false, false);
generator.generateService(definitionPath, serviceName, resourcePath.toString(), filter, options);
if (Files.exists(resourcePath.resolve("openapipetstore_service.bal"))) {
String generatedService = getStringWithNewlineFromGivenBalFile(resourcePath,
Expand Down
Loading

0 comments on commit 1bf8577

Please sign in to comment.