From d9a7df0b19065bb0f9bde75abc798ba3466094df Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 11 Sep 2024 10:26:17 +0530 Subject: [PATCH 01/15] Add sanitize sub command --- .../ballerina/openapi/cmd/CmdConstants.java | 1 + .../io/ballerina/openapi/cmd/OpenApiCmd.java | 2 +- .../io/ballerina/openapi/cmd/Sanitize.java | 314 ++++++++++++++++++ .../cli-help/ballerina-openapi-sanitize.help | 57 ++++ 4 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java create mode 100644 openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java index d68e35b0d..7925adced 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java @@ -206,6 +206,7 @@ public String getValue() { List.of("2.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.1.0"); public static final String DEFAULT_CLIENT_ID = "oas_%s_%s"; public static final String OPENAPI_ADD_CMD = "add"; + public static final String OPENAPI_SANITIZE_CMD = "sanitize"; public enum Mode { SERVICE, diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/OpenApiCmd.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/OpenApiCmd.java index 8689eb5c4..7c64dc809 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/OpenApiCmd.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/OpenApiCmd.java @@ -84,7 +84,7 @@ @CommandLine.Command( name = "openapi", description = "Generate the Ballerina sources for a given OpenAPI definition and vice versa.", - subcommands = {Add.class} + subcommands = {Add.class, Sanitize.class} ) public class OpenApiCmd implements BLauncherCmd { private static final String CMD_NAME = "openapi"; diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java new file mode 100644 index 000000000..f432d2f56 --- /dev/null +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.openapi.cmd; + +import io.ballerina.cli.BLauncherCmd; +import io.ballerina.openapi.core.generators.common.OASModifier; +import io.ballerina.openapi.core.generators.common.exception.BallerinaOpenApiException; +import io.ballerina.openapi.core.generators.common.model.Filter; +import io.ballerina.openapi.service.mapper.utils.CodegenUtils; +import io.swagger.parser.OpenAPIParser; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.core.models.SwaggerParseResult; +import io.swagger.v3.parser.util.InlineModelResolver; +import picocli.CommandLine; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static io.ballerina.openapi.cmd.CmdConstants.JSON_EXTENSION; +import static io.ballerina.openapi.cmd.CmdConstants.OPENAPI_SANITIZE_CMD; +import static io.ballerina.openapi.cmd.CmdConstants.YAML_EXTENSION; +import static io.ballerina.openapi.cmd.CmdConstants.YML_EXTENSION; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE; +import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName; + +/** + * Main class to implement "sanitize" subcommand which is used to flatten and sanitize the OpenAPI definition + * by generating Ballerina friendly type schema names and Ballerina type name extensions. + * + * @since 2.2.0 + */ +@CommandLine.Command( + name = "sanitize", + description = "" +) +public class Sanitize implements BLauncherCmd { + private static final String COMMAND_IDENTIFIER = "openapi-sanitize"; + private static final String COMMA = ","; + + private static final String INFO_OUTPUT_WRITTEN_MSG = "INFO: sanitized OpenAPI definition file was successfully" + + " written to: %s%n"; + private static final String WARNING_INVALID_OUTPUT_FORMAT = "WARNING: invalid output format. The output format" + + " should be either \"json\" or \"yaml\".Defaulting to format of the input file."; + private static final String ERROR_INPUT_PATH_IS_REQUIRED = "ERROR: an OpenAPI definition path is required to " + + "sanitize the OpenAPI definition."; + private static final String ERROR_INVALID_INPUT_FILE_EXTENSION = "ERROR: invalid input OpenAPI definition file " + + "extension. The OpenAPI definition file should be in YAML or JSON format."; + private static final String ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE = "ERROR: error occurred while reading " + + "the OpenAPI definition file."; + private static final String ERROR_UNSUPPORTED_OPENAPI_VERSION = "ERROR: provided OpenAPI contract version is " + + "not supported in the tool. Use OpenAPI specification version 2 or higher"; + private static final String ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE = "ERROR: error occurred while " + + "parsing the OpenAPI definition file."; + private static final String FOUND_PARSER_DIAGNOSTICS = "found the following parser diagnostic messages:"; + private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "ERROR: error occurred while " + + "writing the sanitized OpenAPI definition file"; + private static final String ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES = "ERROR: error occurred while " + + "generating schema names"; + + private PrintStream infoStream = System.out; + private PrintStream errorStream = System.err; + + private Path targetPath = Paths.get(System.getProperty("user.dir")); + private boolean exitWhenFinish = true; + + @CommandLine.Option(names = {"-h", "--help"}, hidden = true) + public boolean helpFlag; + + @CommandLine.Option(names = {"-i", "--input"}, description = "OpenAPI definition file path.") + public String inputPath; + + @CommandLine.Option(names = {"-o", "--output"}, description = "Location of the sanitized OpenAPI definition.") + private String outputPath; + + @CommandLine.Option(names = {"-n", "--name"}, description = "Name of the sanitized OpenAPI definition file.") + private String fileName; + + @CommandLine.Option(names = {"-f", "--format"}, description = "Output format of the sanitized OpenAPI definition.") + private String format; + + @CommandLine.Option(names = {"-t", "--tags"}, description = "Tags that need to be considered when sanitizing.") + public String tags; + + @CommandLine.Option(names = {"--operations"}, description = "Operations that need to be included when sanitizing.") + public String operations; + + public Sanitize() { + } + + public Sanitize(PrintStream errorStream, boolean exitWhenFinish) { + this.errorStream = errorStream; + this.exitWhenFinish = exitWhenFinish; + } + + @Override + public void execute() { + if (helpFlag) { + String commandUsageInfo = BLauncherCmd.getCommandUsageInfo(COMMAND_IDENTIFIER); + infoStream.println(commandUsageInfo); + return; + } + + if (Objects.isNull(inputPath) || inputPath.isBlank()) { + errorStream.println(ERROR_INPUT_PATH_IS_REQUIRED); + exitError(); + return; + } + + if (inputPath.endsWith(YAML_EXTENSION) || inputPath.endsWith(JSON_EXTENSION) || + inputPath.endsWith(YML_EXTENSION)) { + populateInputOptions(); + generateSanitizedOpenAPI(); + return; + } + + errorStream.println(ERROR_INVALID_INPUT_FILE_EXTENSION); + exitError(); + } + + private void populateInputOptions() { + if (Objects.nonNull(format)) { + if (!format.equalsIgnoreCase("json") && !format.equalsIgnoreCase("yaml")) { + setDefaultFormat(); + errorStream.println(WARNING_INVALID_OUTPUT_FORMAT); + } + } else { + setDefaultFormat(); + } + + if (Objects.isNull(fileName)) { + fileName = "sanitized_openapi"; + } + + if (Objects.nonNull(outputPath)) { + targetPath = Paths.get(outputPath).isAbsolute() ? + Paths.get(outputPath) : Paths.get(targetPath.toString(), outputPath); + } + } + + private void setDefaultFormat() { + format = inputPath.endsWith(JSON_EXTENSION) ? "json" : "yaml"; + } + + private void generateSanitizedOpenAPI() { + String openAPIFileContent; + try { + openAPIFileContent = Files.readString(Path.of(inputPath)); + } catch (Exception e) { + errorStream.println(ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE); + exitError(); + return; + } + + Optional openAPIOptional = getSanitizedOpenAPI(openAPIFileContent); + if (openAPIOptional.isEmpty()) { + exitError(); + return; + } + writeSanitizedOpenAPIFile(openAPIOptional.get()); + } + + private Optional getSanitizedOpenAPI(String openAPIFileContent) { + // Read the contents of the file with default parser options + // Sanitizing will be done after filtering the operations + SwaggerParseResult parserResult = new OpenAPIParser().readContents(openAPIFileContent, null, + new ParseOptions()); + if (!parserResult.getMessages().isEmpty() && + parserResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) { + errorStream.println(ERROR_UNSUPPORTED_OPENAPI_VERSION); + return Optional.empty(); + } + + OpenAPI openAPI = parserResult.getOpenAPI(); + if (Objects.isNull(openAPI)) { + errorStream.println(ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE); + if (!parserResult.getMessages().isEmpty()) { + errorStream.println(FOUND_PARSER_DIAGNOSTICS); + parserResult.getMessages().forEach(errorStream::println); + } + return Optional.empty(); + } + + filterOpenAPIOperations(openAPI); + + // Flatten the OpenAPI definition with `flattenComposedSchemas: true` and `camelCaseFlattenNaming: true` + InlineModelResolver inlineModelResolver = new InlineModelResolver(true, true); + inlineModelResolver.flatten(openAPI); + + return sanitizeOpenAPI(openAPI); + } + + private void writeSanitizedOpenAPIFile(OpenAPI openAPI) { + String outputFileNameWithExt = getOutputFileName(); + try { + CodegenUtils.writeFile(targetPath.resolve(outputFileNameWithExt), + outputFileNameWithExt.endsWith(JSON_EXTENSION) ? Json.pretty(openAPI) : Yaml.pretty(openAPI)); + infoStream.printf(INFO_OUTPUT_WRITTEN_MSG, targetPath.resolve(outputFileNameWithExt)); + } catch (IOException exception) { + errorStream.println(ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE); + exitError(); + } + } + + private String getOutputFileName() { + return resolveContractFileName(targetPath, fileName + getFileExtension(), format.equals("json")); + } + + private String getFileExtension() { + return (Objects.nonNull(format) && format.equals("json")) ? JSON_EXTENSION : YAML_EXTENSION; + } + + private void filterOpenAPIOperations(OpenAPI openAPI) { + Filter filter = getFilter(); + if (filter.getOperations().isEmpty() && filter.getTags().isEmpty()) { + return; + } + + // Remove the operations which are not present in the filter + openAPI.getPaths().forEach((path, pathItem) -> pathItem.readOperationsMap() + .forEach((httpMethod, operation) -> { + if (!filter.getOperations().contains(operation.getOperationId()) && + operation.getTags().stream().noneMatch(filter.getTags()::contains)) { + pathItem.operation(httpMethod, null); + } + }) + ); + + // Remove the paths which do not have any operations after filtering + List pathsToRemove = new ArrayList<>(); + openAPI.getPaths().forEach((path, pathItem) -> { + if (pathItem.readOperationsMap().isEmpty()) { + pathsToRemove.add(path); + } + }); + pathsToRemove.forEach(openAPI.getPaths()::remove); + } + + private Optional sanitizeOpenAPI(OpenAPI openAPI) { + OASModifier oasSanitizer = new OASModifier(); + try { + return Optional.of(oasSanitizer.modifyWithBallerinaConventions(openAPI)); + } catch (BallerinaOpenApiException exp) { + errorStream.printf("ERROR: %s%n", exp.getMessage()); + return Optional.empty(); + } + } + + private Filter getFilter() { + List tagList = new ArrayList<>(); + List operationList = new ArrayList<>(); + + if (Objects.nonNull(tags) && !tags.isEmpty()) { + tagList.addAll(Arrays.asList(tags.split(COMMA))); + } + + if (Objects.nonNull(operations) && !operations.isEmpty()) { + operationList.addAll(Arrays.asList(operations.split(COMMA))); + } + + return new Filter(tagList, operationList); + } + + @Override + public String getName() { + return OPENAPI_SANITIZE_CMD; + } + + @Override + public void printLongDesc(StringBuilder stringBuilder) { + //This is the long description of the command and all handle within help command + } + + @Override + public void printUsage(StringBuilder stringBuilder) { + //This is the usage description of the command and all handle within help command + } + + @Override + public void setParentCmdParser(CommandLine commandLine) { + //This is not used in this command + } + + private void exitError() { + if (exitWhenFinish) { + Runtime.getRuntime().exit(1); + } + } +} diff --git a/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help new file mode 100644 index 000000000..d7b96d886 --- /dev/null +++ b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help @@ -0,0 +1,57 @@ +NAME + bal openapi sanitize - Sanitize the OpenAPI contract file according to + the best practices of Ballerina. + +SYNOPSIS + bal openapi sanitize [-i | --input] + [-o | --output] + [-n | --name] + [-f | --format] [json|yaml] + [-t | --tags] + [--operations] + +DESCRIPTION + Sanitize the OpenAPI contract file according to the best practices of + Ballerina. The OpenAPI contract is flatten and the type schema names + are made Ballerina friendly. The Ballerina name extensions are added + to the schemas which can not be modified. + + +OPTIONS + -i, --input + This is a mandatory input. The given OpenAPI contract will be sanitized. + The OpenAPI contract can be either a YAML or a JSON. + + -o, --output + This is an optional input. The given output file path will be used to + save the sanitized OpenAPI contract. The default output file path is + the executed directory. + + -n, --name + This is an optional input. The given name will be used to save the + sanitized OpenAPI contract. The default name is `sanitized_openapi`. + + -f, --format [json|yaml] + This is an optional input. The sanitized OpenAPI contract will be + saved in the given format. The format can be either JSON or YAML. + The default format is same as the input file format. + + -t, --tags + This is an optional input. The sanitized OpenAPI contract will only + have the operations with the given tags. + + --operations + This is an optional input. The sanitized OpenAPI contract will only + have the given operations. + +EXAMPLES + Sanitize the `service.yaml` OpenAPI contract file. + $ bal openapi sanitize -i service.yaml + + Sanitize the `service.yaml` OpenAPI contract file and save it as + `sanitized_svc.json` file. + $ bal openapi sanitize -i hello.yaml -n sanitized_svc -f json + + Sanitize the `service.json` OpenAPI contract file by filtering the + operations with the `service` tag. + $ bal openapi sanitize -i service.json -t service From 51cdf3ceb5637433f68fe242eb3dd0bd1c4e9a6a Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 11 Sep 2024 12:04:21 +0530 Subject: [PATCH 02/15] Add support for adding ballerina name extension --- .../core/generators/common/OASModifier.java | 92 ++++++++++++++++--- 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java index d4ad288f7..e9b775f41 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java @@ -106,20 +106,42 @@ private static Map getResolvedNameMapping(Map na public OpenAPI modifyWithBallerinaConventions(OpenAPI openapi, Map nameMap) throws BallerinaOpenApiException { - // This is for data type name modification openapi = modifyOASWithSchemaName(openapi, nameMap); + modifyOASWithParameterName(openapi); + modifyOASWithObjectPropertyName(openapi); + return openapi; + } + + private static void modifyOASWithObjectPropertyName(OpenAPI openapi) { + Components components = openapi.getComponents(); + if (Objects.isNull(components) || Objects.isNull(components.getSchemas())) { + return; + } + + Map schemas = components.getSchemas(); + for (Map.Entry schema : schemas.entrySet()) { + Schema schemaValue = schema.getValue(); + if (schemaValue instanceof ObjectSchema objectSchema) { + Map properties = objectSchema.getProperties(); + if (properties != null && !properties.isEmpty()) { + objectSchema.setProperties(getPropertiesWithBallerinaNameExtension(properties)); + } + } + } + } + + private static void modifyOASWithParameterName(OpenAPI openapi) { Paths paths = openapi.getPaths(); if (paths == null || paths.isEmpty()) { - return openapi; + return; } - // This is for path parameter name modifications + Paths modifiedPaths = new Paths(); for (Map.Entry path : paths.entrySet()) { PathDetails result = updateParameterNameDetails(path); modifiedPaths.put(result.pathValue(), result.pathItem()); } openapi.setPaths(modifiedPaths); - return openapi; } public OpenAPI modifyWithBallerinaConventions(OpenAPI openapi) throws BallerinaOpenApiException { @@ -268,19 +290,67 @@ private static String updateParameters(String pathValue, Map par List parameters, List modifiedParameters) { for (Parameter parameter : parameters) { - if (!parameter.getIn().equals("path")) { - modifiedParameters.add(parameter); - continue; + String location = parameter.getIn(); + if ("path".equals(location)) { + String modifiedPathParam = parameterNames.get(parameter.getName()); + parameter.setName(modifiedPathParam); + pathValue = replaceContentInBraces(pathValue, modifiedPathParam); + } else if ("query".equals(location) || "header".equals(location)) { + addBallerinaNameExtension(parameter); } - String oasPathParamName = parameter.getName(); - String modifiedPathParam = parameterNames.get(oasPathParamName); - parameter.setName(modifiedPathParam); modifiedParameters.add(parameter); - pathValue = replaceContentInBraces(pathValue, modifiedPathParam); } return pathValue; } + private static void addBallerinaNameExtension(Parameter parameter) { + String parameterName = parameter.getName(); + if (Objects.isNull(parameterName)) { + return; + } + + String sanitizedName = getValidNameForParameter(parameterName); + if (parameterName.equals(sanitizedName)) { + return; + } + + if (Objects.isNull(parameter.getExtensions())) { + parameter.setExtensions(new HashMap<>()); + } + parameter.getExtensions().put("x-ballerina-name", sanitizedName); + } + + private static Schema getSchemaWithBallerinaNameExtension(String propertyName, Schema propertySchema) { + String sanitizedPropertyName = getValidNameForParameter(propertyName); + if (propertyName.equals(sanitizedPropertyName)) { + return propertySchema; + } + + if (Objects.nonNull(propertySchema.get$ref())) { + Schema refSchema = new Schema<>(); + refSchema.set$ref(propertySchema.get$ref()); + propertySchema.set$ref(null); + propertySchema.addAllOfItem(refSchema); + propertySchema.setType(null); + } + + if (Objects.isNull(propertySchema.getExtensions())) { + propertySchema.setExtensions(new HashMap<>()); + } + propertySchema.getExtensions().put("x-ballerina-name", sanitizedPropertyName); + return propertySchema; + } + + private static Map getPropertiesWithBallerinaNameExtension(Map properties) { + Map modifiedProperties = new HashMap<>(); + for (Map.Entry property : properties.entrySet()) { + Schema propertySchema = property.getValue(); + String propertyName = property.getKey(); + modifiedProperties.put(propertyName, getSchemaWithBallerinaNameExtension(propertyName, propertySchema)); + } + return modifiedProperties; + } + /** * Record for storing return data. * From 1062b6a7ef14e77a86d4718276311af62d4e1e6e Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 11 Sep 2024 13:04:13 +0530 Subject: [PATCH 03/15] Add test cases --- .../openapi/cmd/NegativeCmdTests.java | 55 +++ .../ballerina/openapi/cmd/OpenAPICmdTest.java | 112 ++++++ .../test/resources/cmd/sanitize/openapi.json | 300 +++++++++++++++ .../test/resources/cmd/sanitize/openapi.yaml | 189 +++++++++ .../cmd/sanitize/openapi_composed_schema.json | 334 ++++++++++++++++ .../cmd/sanitize/openapi_invalid.json | 189 +++++++++ .../sanitized_openapi_composed_schema.json | 360 ++++++++++++++++++ .../sanitize/sanitized_openapi_expected.json | 313 +++++++++++++++ .../sanitize/sanitized_openapi_expected.yaml | 202 ++++++++++ .../sanitized_openapi_expected_1.json | 278 ++++++++++++++ .../sanitized_openapi_expected_1.yaml | 202 ++++++++++ .../sanitized_openapi_expected_albums.json | 271 +++++++++++++ ...sanitized_openapi_expected_operations.json | 233 ++++++++++++ 13 files changed, 3038 insertions(+) create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/openapi.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/openapi.yaml create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/openapi_composed_schema.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/openapi_invalid.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java index 65f108e16..b2e9ceae1 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java @@ -43,4 +43,59 @@ public void testInvalidBallerinaPackage() throws IOException { String output = readOutput(true); Assert.assertTrue(output.contains("ERROR: invalid Ballerina package directory:")); } + + @Test(description = "Test without the input OpenAPI file in `sanitize` sub command") + public void testSanitizeWithOutInputOpenAPIFile() throws IOException { + String[] addArgs = {"-f", "json"}; + Sanitize sanitize = new Sanitize(printStream, false); + new CommandLine(sanitize).parseArgs(addArgs); + sanitize.execute(); + String output = readOutput(true); + Assert.assertTrue(output.contains("ERROR: an OpenAPI definition path is required to sanitize the OpenAPI " + + "definition.")); + } + + @Test(description = "Test with the invalid input OpenAPI file in `sanitize` sub command") + public void testSanitizeWithInvalidInputOpenAPIFile() throws IOException { + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi-1.json"}; + Sanitize sanitize = new Sanitize(printStream, false); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + String output = readOutput(true); + Assert.assertTrue(output.contains("ERROR: error occurred while reading the OpenAPI definition file.")); + } + + @Test(description = "Test with invalid invalid input OpenAPI file extension in `sanitize` sub command") + public void testSanitizeWithInvalidInputOpenAPIFileExtension() throws IOException { + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.txt"}; + Sanitize sanitize = new Sanitize(printStream, false); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + String output = readOutput(true); + Assert.assertTrue(output.contains("ERROR: invalid input OpenAPI definition file extension. The OpenAPI " + + "definition file should be in YAML or JSON format.")); + } + + @Test(description = "Test with the invalid output OpenAPI file format in `sanitize` sub command") + public void testSanitizeWithInvalidOutputOpenAPIFileFormat() throws IOException { + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.json", "-f", "txt", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(printStream, false); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + String output = readOutput(true); + Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + + "\"json\" or \"yaml\".Defaulting to format of the input file.")); + } + + @Test(description = "Test with the input OpenAPI file in `sanitize` sub command which has parsing issues") + public void testSanitizeWithInputOpenAPIFileParsingIssues() throws IOException { + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi_invalid.json", "-f", "txt", "-o", + tmpDir.toString()}; + Sanitize sanitize = new Sanitize(printStream, false); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + String output = readOutput(true); + Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + + "\"json\" or \"yaml\".Defaulting to format of the input file.")); + } } diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java index 980ce071a..44dae9d21 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java @@ -915,6 +915,118 @@ public void testAddCmd() throws IOException { Assert.assertTrue(tomlContent.contains(generatedTool)); } + @Test(description = "Test openapi sanitize sub command with default options with the json file") + public void testSanitizeCmdDefaultJson() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_expected.json")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.json", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.json")); + } + + private void compareFiles(Path expectedFilePath, Path generatedFilePath) throws IOException { + if (Files.exists(generatedFilePath)) { + String expectedOpenAPIContent = ""; + try (Stream expectedOpenAPIContentLines = Files.lines(expectedFilePath)) { + expectedOpenAPIContent = expectedOpenAPIContentLines.collect(Collectors.joining(LINE_SEPARATOR)); + } catch (IOException e) { + Files.deleteIfExists(generatedFilePath); + Assert.fail(e.getMessage()); + } + + String generatedOpenAPIContent = ""; + try (Stream generatedOpenAPIContentLines = + Files.lines(generatedFilePath)) { + generatedOpenAPIContent = generatedOpenAPIContentLines.collect(Collectors.joining(LINE_SEPARATOR)); + } catch (IOException e) { + Files.deleteIfExists(generatedFilePath); + Assert.fail(e.getMessage()); + } + + generatedOpenAPIContent = (generatedOpenAPIContent.trim()).replaceAll("\\s+", ""); + expectedOpenAPIContent = (expectedOpenAPIContent.trim()).replaceAll("\\s+", ""); + Assert.assertEquals(expectedOpenAPIContent, generatedOpenAPIContent); + + Files.deleteIfExists(generatedFilePath); + } else { + Assert.fail("Generation failed: " + readOutput(true)); + } + } + + @Test(description = "Test openapi sanitize sub command with default options with the yaml file") + public void testSanitizeCmdDefaultYaml() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_expected.yaml")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.yaml", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.yaml")); + } + + @Test(description = "Test openapi sanitize sub command with the name option") + public void testSanitizeCmdName() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_expected.json")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.json", "-o", tmpDir.toString(), "-n", "sanitized"}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized.json")); + } + + @Test(description = "Test openapi sanitize sub command with the json format option") + public void testSanitizeCmdJsonFormat() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_expected_1.json")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.yaml", "-o", tmpDir.toString(), "-f", "json"}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.json")); + } + + @Test(description = "Test openapi sanitize sub command with the yaml format option") + public void testSanitizeCmdYamlFormat() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_expected_1.yaml")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.json", "-o", tmpDir.toString(), "-f", "yaml"}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.yaml")); + } + + @Test(description = "Test openapi sanitize sub command with the tags option") + public void testSanitizeCmdTags() throws IOException { + Path expectedFilePath = resourceDir.resolve( + Paths.get("cmd/sanitize/sanitized_openapi_expected_albums.json")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.json", "-o", tmpDir.toString(), "-t", "albums"}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.json")); + } + + @Test(description = "Test openapi sanitize sub command with the operations option") + public void testSanitizeCmdOperations() throws IOException { + Path expectedFilePath = resourceDir.resolve( + Paths.get("cmd/sanitize/sanitized_openapi_expected_operations.json")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi.json", "-o", tmpDir.toString(), "--operations", + "getAlbumById,getAlbums"}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.json")); + } + + @Test(description = "Test openapi sanitize sub command with composed schema") + public void testSanitizeCmdWithComposedSchema() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_composed_schema.json")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi_composed_schema.json", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.json")); + } + @AfterTest public void clean() { System.setErr(null); diff --git a/openapi-cli/src/test/resources/cmd/sanitize/openapi.json b/openapi-cli/src/test/resources/cmd/sanitize/openapi.json new file mode 100644 index 000000000..a7146e2b2 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/openapi.json @@ -0,0 +1,300 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api V1", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://{server}:{port}/api/v1", + "variables": { + "server": { + "default": "localhost" + }, + "port": { + "default": "8080" + } + } + } + ], + "paths": { + "/albums": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbums", + "parameters": [ + { + "name": "_artists_", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + { + "name": "X-API-VERSION", + "in": "header", + "schema": { + "type": "string", + "default": "v1" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/album" + } + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + }, + "post": { + "tags": [ + "albums" + ], + "operationId": "postAlbum", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/album" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{_id}": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbumById", + "parameters": [ + { + "name": "_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + }, + "404": { + "description": "NotFound", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/message" + } + } + } + } + } + } + }, + "/albums/{id}/artist": { + "get": { + "tags": [ + "artists" + ], + "operationId": "getArtistByAlbum", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/album_aRTIST" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "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" + } + } + }, + "album": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "artist": { + "type": "string" + } + }, + "additionalProperties": false + }, + "album_aRTIST": { + "required": [ + "albums", + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "albums": { + "type": "array", + "items": { + "$ref": "#/components/schemas/album" + } + } + }, + "additionalProperties": false + }, + "message": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + } + } + } +} diff --git a/openapi-cli/src/test/resources/cmd/sanitize/openapi.yaml b/openapi-cli/src/test/resources/cmd/sanitize/openapi.yaml new file mode 100644 index 000000000..522fcba50 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/openapi.yaml @@ -0,0 +1,189 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + schema: + type: array + items: + type: string + default: [] + - name: X-API-VERSION + in: header + schema: + type: string + default: v1 + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{_id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: _id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/album_aRTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + 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 + album: + required: + - _id + - artist + - title + type: object + properties: + _id: + type: string + title: + type: string + artist: + type: string + additionalProperties: false + album_aRTIST: + required: + - albums + - id + - name + type: object + properties: + id: + type: string + name: + type: string + albums: + type: array + items: + $ref: "#/components/schemas/album" + additionalProperties: false + message: + required: + - code + - message + type: object + properties: + message: + type: string + code: + type: integer + format: int64 + additionalProperties: false diff --git a/openapi-cli/src/test/resources/cmd/sanitize/openapi_composed_schema.json b/openapi-cli/src/test/resources/cmd/sanitize/openapi_composed_schema.json new file mode 100644 index 000000000..bf423db3d --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/openapi_composed_schema.json @@ -0,0 +1,334 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api V1", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://{server}:{port}/api/v1", + "variables": { + "server": { + "default": "localhost" + }, + "port": { + "default": "8080" + } + } + } + ], + "paths": { + "/albums": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbums", + "parameters": [ + { + "name": "_artists_", + "in": "query", + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + } + }, + { + "name": "X-API-VERSION", + "in": "header", + "schema": { + "type": "string", + "default": "v1" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/album" + } + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + }, + "post": { + "tags": [ + "albums" + ], + "operationId": "postAlbum", + "requestBody": { + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "artist": { + "type": "string" + } + }, + "additionalProperties": false + }, + { + "required": [ + "artist", + "title" + ], + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "artist": { + "type": "string" + } + }, + "additionalProperties": false + } + ] + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{_id}": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbumById", + "parameters": [ + { + "name": "_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + }, + "404": { + "description": "NotFound", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/message" + } + } + } + } + } + } + }, + "/albums/{id}/artist": { + "get": { + "tags": [ + "artists" + ], + "operationId": "getArtistByAlbum", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "required": [ + "albums", + "id", + "name" + ], + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "albums": { + "type": "array", + "items": { + "$ref": "#/components/schemas/album" + } + } + }, + "additionalProperties": false + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "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" + } + } + }, + "album": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "_id": { + "type": "string" + }, + "title": { + "type": "string" + }, + "artist": { + "type": "string" + } + }, + "additionalProperties": false + }, + "message": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "integer", + "format": "int64" + } + }, + "additionalProperties": false + } + } + } +} diff --git a/openapi-cli/src/test/resources/cmd/sanitize/openapi_invalid.json b/openapi-cli/src/test/resources/cmd/sanitize/openapi_invalid.json new file mode 100644 index 000000000..522fcba50 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/openapi_invalid.json @@ -0,0 +1,189 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + schema: + type: array + items: + type: string + default: [] + - name: X-API-VERSION + in: header + schema: + type: string + default: v1 + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{_id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: _id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/album_aRTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + 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 + album: + required: + - _id + - artist + - title + type: object + properties: + _id: + type: string + title: + type: string + artist: + type: string + additionalProperties: false + album_aRTIST: + required: + - albums + - id + - name + type: object + properties: + id: + type: string + name: + type: string + albums: + type: array + items: + $ref: "#/components/schemas/album" + additionalProperties: false + message: + required: + - code + - message + type: object + properties: + message: + type: string + code: + type: integer + format: int64 + additionalProperties: false diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json new file mode 100644 index 000000000..3d9ec87b7 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json @@ -0,0 +1,360 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api V1", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://{server}:{port}/api/v1", + "variables": { + "server": { + "default": "localhost" + }, + "port": { + "default": "8080" + } + } + } + ], + "paths": { + "/albums": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbums", + "parameters": [ + { + "name": "_artists_", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "x-ballerina-name": "artists" + }, + { + "name": "X-API-VERSION", + "in": "header", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string", + "default": "v1" + }, + "x-ballerina-name": "xApiVersion" + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + }, + "post": { + "tags": [ + "albums" + ], + "operationId": "postAlbum", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumsBody" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{id}": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbumById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + }, + "404": { + "description": "NotFound", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + }, + "/albums/{id}/artist": { + "get": { + "tags": [ + "artists" + ], + "operationId": "getArtistByAlbum", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InlineResponse200" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ErrorPayload": { + "required": [ + "message", + "method", + "path", + "reason", + "status", + "timestamp" + ], + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int64" + } + } + }, + "AlbumsBody": { + "oneOf": [ + { + "$ref": "#/components/schemas/AlbumsOneOf1" + }, + { + "$ref": "#/components/schemas/AlbumsalbumsOneOf12" + } + ] + }, + "Message": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + }, + "additionalProperties": false + }, + "AlbumsalbumsOneOf12": { + "required": [ + "artist", + "title" + ], + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Album": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "_id": { + "type": "string", + "x-ballerina-name": "id" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + }, + "InlineResponse200": { + "required": [ + "albums", + "id", + "name" + ], + "type": "object", + "properties": { + "albums": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "AlbumsOneOf1": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "_id": { + "type": "string", + "x-ballerina-name": "id" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } +} diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json new file mode 100644 index 000000000..7f538988d --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json @@ -0,0 +1,313 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api V1", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://{server}:{port}/api/v1", + "variables": { + "server": { + "default": "localhost" + }, + "port": { + "default": "8080" + } + } + } + ], + "paths": { + "/albums": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbums", + "parameters": [ + { + "name": "_artists_", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "x-ballerina-name": "artists" + }, + { + "name": "X-API-VERSION", + "in": "header", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string", + "default": "v1" + }, + "x-ballerina-name": "xApiVersion" + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + }, + "post": { + "tags": [ + "albums" + ], + "operationId": "postAlbum", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{id}": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbumById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + }, + "404": { + "description": "NotFound", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + }, + "/albums/{id}/artist": { + "get": { + "tags": [ + "artists" + ], + "operationId": "getArtistByAlbum", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AlbumARTIST" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AlbumARTIST": { + "required": [ + "albums", + "id", + "name" + ], + "type": "object", + "properties": { + "albums": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ErrorPayload": { + "required": [ + "message", + "method", + "path", + "reason", + "status", + "timestamp" + ], + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int64" + } + } + }, + "Message": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Album": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "_id": { + "type": "string", + "x-ballerina-name": "id" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } +} diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml new file mode 100644 index 000000000..e5d36eb9c --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml @@ -0,0 +1,202 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + required: false + style: form + explode: true + schema: + type: array + items: + type: string + default: [] + x-ballerina-name: artists + - name: X-API-VERSION + in: header + required: false + style: simple + explode: false + schema: + type: string + default: v1 + x-ballerina-name: xApiVersion + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/Message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/AlbumARTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + AlbumARTIST: + required: + - albums + - id + - name + type: object + properties: + albums: + type: array + items: + $ref: "#/components/schemas/Album" + name: + type: string + id: + type: string + additionalProperties: false + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + reason: + type: string + path: + type: string + method: + type: string + message: + type: string + timestamp: + type: string + status: + type: integer + format: int64 + Message: + required: + - code + - message + type: object + properties: + code: + type: integer + format: int64 + message: + type: string + additionalProperties: false + Album: + required: + - _id + - artist + - title + type: object + properties: + artist: + type: string + _id: + type: string + x-ballerina-name: id + title: + type: string + additionalProperties: false diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json new file mode 100644 index 000000000..fafc3054a --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json @@ -0,0 +1,278 @@ +{ + "openapi" : "3.0.1", + "info" : { + "title" : "Api V1", + "version" : "0.0.0" + }, + "servers" : [ { + "url" : "http://{server}:{port}/api/v1", + "variables" : { + "server" : { + "default" : "localhost" + }, + "port" : { + "default" : "8080" + } + } + } ], + "paths" : { + "/albums" : { + "get" : { + "tags" : [ "albums" ], + "operationId" : "getAlbums", + "parameters" : [ { + "name" : "_artists_", + "in" : "query", + "required" : false, + "style" : "form", + "explode" : true, + "schema" : { + "type" : "array", + "items" : { + "type" : "string" + }, + "default" : [ ] + }, + "x-ballerina-name" : "artists" + }, { + "name" : "X-API-VERSION", + "in" : "header", + "required" : false, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string", + "default" : "v1" + }, + "x-ballerina-name" : "xApiVersion" + } ], + "responses" : { + "200" : { + "description" : "Ok", + "content" : { + "application/json" : { + "schema" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Album" + } + } + } + } + }, + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" + } + } + } + } + } + }, + "post" : { + "tags" : [ "albums" ], + "operationId" : "postAlbum", + "requestBody" : { + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Album" + } + } + }, + "required" : true + }, + "responses" : { + "201" : { + "description" : "Created", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Album" + } + } + } + }, + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{id}" : { + "get" : { + "tags" : [ "albums" ], + "operationId" : "getAlbumById", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Ok", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Album" + } + } + } + }, + "404" : { + "description" : "NotFound", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Message" + } + } + } + }, + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{id}/artist" : { + "get" : { + "tags" : [ "artists" ], + "operationId" : "getArtistByAlbum", + "parameters" : [ { + "name" : "id", + "in" : "path", + "required" : true, + "style" : "simple", + "explode" : false, + "schema" : { + "type" : "string" + } + } ], + "responses" : { + "200" : { + "description" : "Ok", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/AlbumARTIST" + } + } + } + }, + "400" : { + "description" : "BadRequest", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + } + }, + "components" : { + "schemas" : { + "AlbumARTIST" : { + "required" : [ "albums", "id", "name" ], + "type" : "object", + "properties" : { + "albums" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Album" + } + }, + "name" : { + "type" : "string" + }, + "id" : { + "type" : "string" + } + }, + "additionalProperties" : false + }, + "ErrorPayload" : { + "required" : [ "message", "method", "path", "reason", "status", "timestamp" ], + "type" : "object", + "properties" : { + "reason" : { + "type" : "string" + }, + "path" : { + "type" : "string" + }, + "method" : { + "type" : "string" + }, + "message" : { + "type" : "string" + }, + "timestamp" : { + "type" : "string" + }, + "status" : { + "type" : "integer", + "format" : "int64" + } + } + }, + "Message" : { + "required" : [ "code", "message" ], + "type" : "object", + "properties" : { + "code" : { + "type" : "integer", + "format" : "int64" + }, + "message" : { + "type" : "string" + } + }, + "additionalProperties" : false + }, + "Album" : { + "required" : [ "_id", "artist", "title" ], + "type" : "object", + "properties" : { + "artist" : { + "type" : "string" + }, + "_id" : { + "type" : "string", + "x-ballerina-name" : "id" + }, + "title" : { + "type" : "string" + } + }, + "additionalProperties" : false + } + } + } +} diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml new file mode 100644 index 000000000..3117d23e0 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml @@ -0,0 +1,202 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + required: false + style: form + explode: true + schema: + type: array + items: + type: string + default: [] + x-ballerina-name: artists + - name: X-API-VERSION + in: header + required: false + style: simple + explode: false + schema: + type: string + default: v1 + x-ballerina-name: xApiVersion + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/Message" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/AlbumARTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + AlbumARTIST: + required: + - albums + - id + - name + type: object + properties: + albums: + type: array + items: + $ref: "#/components/schemas/Album" + name: + type: string + id: + type: string + additionalProperties: false + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + reason: + type: string + path: + type: string + method: + type: string + message: + type: string + timestamp: + type: string + status: + type: integer + format: int64 + Message: + required: + - code + - message + type: object + properties: + code: + type: integer + format: int64 + message: + type: string + additionalProperties: false + Album: + required: + - _id + - artist + - title + type: object + properties: + artist: + type: string + _id: + type: string + x-ballerina-name: id + title: + type: string + additionalProperties: false diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json new file mode 100644 index 000000000..1dbe56916 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json @@ -0,0 +1,271 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api V1", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://{server}:{port}/api/v1", + "variables": { + "server": { + "default": "localhost" + }, + "port": { + "default": "8080" + } + } + } + ], + "paths": { + "/albums": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbums", + "parameters": [ + { + "name": "_artists_", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "x-ballerina-name": "artists" + }, + { + "name": "X-API-VERSION", + "in": "header", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string", + "default": "v1" + }, + "x-ballerina-name": "xApiVersion" + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + }, + "post": { + "tags": [ + "albums" + ], + "operationId": "postAlbum", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{id}": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbumById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + }, + "404": { + "description": "NotFound", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AlbumARTIST": { + "required": [ + "albums", + "id", + "name" + ], + "type": "object", + "properties": { + "albums": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ErrorPayload": { + "required": [ + "message", + "method", + "path", + "reason", + "status", + "timestamp" + ], + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int64" + } + } + }, + "Message": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Album": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "_id": { + "type": "string", + "x-ballerina-name": "id" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } +} diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json new file mode 100644 index 000000000..4f5d84be7 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json @@ -0,0 +1,233 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Api V1", + "version": "0.0.0" + }, + "servers": [ + { + "url": "http://{server}:{port}/api/v1", + "variables": { + "server": { + "default": "localhost" + }, + "port": { + "default": "8080" + } + } + } + ], + "paths": { + "/albums": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbums", + "parameters": [ + { + "name": "_artists_", + "in": "query", + "required": false, + "style": "form", + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + }, + "default": [] + }, + "x-ballerina-name": "artists" + }, + { + "name": "X-API-VERSION", + "in": "header", + "required": false, + "style": "simple", + "explode": false, + "schema": { + "type": "string", + "default": "v1" + }, + "x-ballerina-name": "xApiVersion" + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + } + } + } + }, + "/albums/{id}": { + "get": { + "tags": [ + "albums" + ], + "operationId": "getAlbumById", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "style": "simple", + "explode": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Ok", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Album" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorPayload" + } + } + } + }, + "404": { + "description": "NotFound", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Message" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "AlbumARTIST": { + "required": [ + "albums", + "id", + "name" + ], + "type": "object", + "properties": { + "albums": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Album" + } + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "ErrorPayload": { + "required": [ + "message", + "method", + "path", + "reason", + "status", + "timestamp" + ], + "type": "object", + "properties": { + "reason": { + "type": "string" + }, + "path": { + "type": "string" + }, + "method": { + "type": "string" + }, + "message": { + "type": "string" + }, + "timestamp": { + "type": "string" + }, + "status": { + "type": "integer", + "format": "int64" + } + } + }, + "Message": { + "required": [ + "code", + "message" + ], + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + } + }, + "additionalProperties": false + }, + "Album": { + "required": [ + "_id", + "artist", + "title" + ], + "type": "object", + "properties": { + "artist": { + "type": "string" + }, + "_id": { + "type": "string", + "x-ballerina-name": "id" + }, + "title": { + "type": "string" + } + }, + "additionalProperties": false + } + } + } +} From 8ba86aee90aae690f34a6115fb044a01f7d70089 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 11 Sep 2024 13:05:54 +0530 Subject: [PATCH 04/15] Update command description --- .../resources/cli-help/ballerina-openapi-sanitize.help | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help index d7b96d886..d2154a38a 100644 --- a/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help +++ b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help @@ -11,10 +11,11 @@ SYNOPSIS [--operations] DESCRIPTION - Sanitize the OpenAPI contract file according to the best practices of - Ballerina. The OpenAPI contract is flatten and the type schema names - are made Ballerina friendly. The Ballerina name extensions are added - to the schemas which can not be modified. + Sanitize the OpenAPI contract file according to the best naming + practices of Ballerina. The OpenAPI contract is flatten and the type + schema names are made Ballerina friendly. The Ballerina name + extensions are added to the schemas which can not be modified + directly. OPTIONS From 68311bb89932abd7995d05e68909a21fe2ddb301 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Wed, 11 Sep 2024 14:24:17 +0530 Subject: [PATCH 05/15] Fixed test cases --- .../test/resources/expected_gen/sanitize_array_member.bal | 8 ++++---- .../resources/expected_gen/type_name_with_mixed_case.bal | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal b/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal index ea4687c30..7c972f5a5 100644 --- a/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal +++ b/openapi-cli/src/test/resources/expected_gen/sanitize_array_member.bal @@ -280,14 +280,14 @@ public type ClientHttp1Settings record {| public type VERSION "V1"|"V2"; public type Album record {| - # Album ID - string id; - # Album name - string name; # Album artist string artist; + # Album name + string name; # Album genre string genre; + # Album ID + string id; |}; # Represents the Queries record for the operation: getAlbums diff --git a/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal b/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal index 3dbd1a9f9..a54b92a74 100644 --- a/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal +++ b/openapi-cli/src/test/resources/expected_gen/type_name_with_mixed_case.bal @@ -267,14 +267,14 @@ isolated function getMapForHeaders(map headerParam) returns map Date: Wed, 11 Sep 2024 15:33:47 +0530 Subject: [PATCH 06/15] Add suggestions from review --- .../io/ballerina/openapi/cmd/Sanitize.java | 3 +- .../generators/common/GeneratorConstants.java | 1 + .../core/generators/common/OASModifier.java | 69 ++++++++++--------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java index f432d2f56..ef6cf091f 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java @@ -82,6 +82,7 @@ public class Sanitize implements BLauncherCmd { "writing the sanitized OpenAPI definition file"; private static final String ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES = "ERROR: error occurred while " + "generating schema names"; + private static final String DEFAULT_FILE_NAME = "sanitized_openapi"; private PrintStream infoStream = System.out; private PrintStream errorStream = System.err; @@ -154,7 +155,7 @@ private void populateInputOptions() { } if (Objects.isNull(fileName)) { - fileName = "sanitized_openapi"; + fileName = DEFAULT_FILE_NAME; } if (Objects.nonNull(outputPath)) { diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java index ab185bf08..f8e1ccba0 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/GeneratorConstants.java @@ -423,4 +423,5 @@ public String getValue() { public static final String DECIMAL = "decimal"; public static final String RETURN = "return"; public static final String OPTIONAL_ERROR = "error?"; + public static final String PATH = "path"; } diff --git a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java index e9b775f41..108d2f82c 100644 --- a/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java +++ b/openapi-core/src/main/java/io/ballerina/openapi/core/generators/common/OASModifier.java @@ -46,6 +46,10 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.HEADER; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.PATH; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.QUERY; + /** * This class is to contain the sanitized utils for naming. * @@ -57,6 +61,7 @@ public class OASModifier { private static final String COMPONENT_REF_REGEX_PATTERN = "(\\\"\\$ref\\\"\\s*:\\s*\\\"#/components/schemas/%s" + "\\\")|('\\$ref'\\s*:\\s*'#/components/schemas/%s')"; private static final PrintStream outErrorStream = System.err; + private static final String BALLERINA_NAME_EXT = "x-ballerina-name"; List diagnostics = new ArrayList<>(); @@ -66,12 +71,12 @@ public List getDiagnostics() { public Map getProposedNameMapping(OpenAPI openapi) { Map nameMap = new HashMap<>(); - if (openapi.getComponents() == null) { + if (Objects.isNull(openapi.getComponents())) { return Collections.emptyMap(); } Components components = openapi.getComponents(); Map schemas = components.getSchemas(); - if (schemas == null) { + if (Objects.isNull(schemas)) { return Collections.emptyMap(); } @@ -123,7 +128,7 @@ private static void modifyOASWithObjectPropertyName(OpenAPI openapi) { Schema schemaValue = schema.getValue(); if (schemaValue instanceof ObjectSchema objectSchema) { Map properties = objectSchema.getProperties(); - if (properties != null && !properties.isEmpty()) { + if (Objects.nonNull(properties) && !properties.isEmpty()) { objectSchema.setProperties(getPropertiesWithBallerinaNameExtension(properties)); } } @@ -132,7 +137,7 @@ private static void modifyOASWithObjectPropertyName(OpenAPI openapi) { private static void modifyOASWithParameterName(OpenAPI openapi) { Paths paths = openapi.getPaths(); - if (paths == null || paths.isEmpty()) { + if (Objects.isNull(paths) || paths.isEmpty()) { return; } @@ -208,65 +213,65 @@ private static OpenAPI modifyOASWithSchemaName(OpenAPI openapi, Map path) { PathItem pathItem = path.getValue(); String pathValue = path.getKey(); - if (pathItem.getGet() != null) { + if (Objects.nonNull(pathItem.getGet())) { Operation operation = pathItem.getGet(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setGet(operation); } - if (pathItem.getPost() != null) { + if (Objects.nonNull(pathItem.getPost())) { Operation operation = pathItem.getPost(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setPost(operation); } - if (pathItem.getPut() != null) { + if (Objects.nonNull(pathItem.getPut())) { Operation operation = pathItem.getPut(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setPut(operation); } - if (pathItem.getDelete() != null) { + if (Objects.nonNull(pathItem.getDelete())) { Operation operation = pathItem.getDelete(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setDelete(operation); } - if (pathItem.getPatch() != null) { + if (Objects.nonNull(pathItem.getPatch())) { Operation operation = pathItem.getPatch(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setPatch(operation); } - if (pathItem.getHead() != null) { + if (Objects.nonNull(pathItem.getHead())) { Operation operation = pathItem.getHead(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setHead(operation); } - if (pathItem.getOptions() != null) { + if (Objects.nonNull(pathItem.getOptions())) { Operation operation = pathItem.getOptions(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); pathItem.setOptions(operation); } - if (pathItem.getTrace() != null) { + if (Objects.nonNull(pathItem.getTrace())) { Operation operation = pathItem.getTrace(); - if (operation.getParameters() == null) { + if (Objects.isNull(operation.getParameters())) { return new PathDetails(pathItem, pathValue); } pathValue = updateParameterNames(pathValue, operation); @@ -278,7 +283,7 @@ private static PathDetails updateParameterNameDetails(Map.Entry parameterNames = collectParameterNames(operation.getParameters()); List parameters = operation.getParameters(); - if (parameters != null) { + if (Objects.nonNull(parameters)) { List modifiedParameters = new ArrayList<>(); pathValue = updateParameters(pathValue, parameterNames, parameters, modifiedParameters); operation.setParameters(modifiedParameters); @@ -291,11 +296,11 @@ private static String updateParameters(String pathValue, Map par for (Parameter parameter : parameters) { String location = parameter.getIn(); - if ("path".equals(location)) { + if (location.equals(PATH)) { String modifiedPathParam = parameterNames.get(parameter.getName()); parameter.setName(modifiedPathParam); pathValue = replaceContentInBraces(pathValue, modifiedPathParam); - } else if ("query".equals(location) || "header".equals(location)) { + } else if (location.equals(QUERY) || location.equals(HEADER)) { addBallerinaNameExtension(parameter); } modifiedParameters.add(parameter); @@ -317,7 +322,7 @@ private static void addBallerinaNameExtension(Parameter parameter) { if (Objects.isNull(parameter.getExtensions())) { parameter.setExtensions(new HashMap<>()); } - parameter.getExtensions().put("x-ballerina-name", sanitizedName); + parameter.getExtensions().put(BALLERINA_NAME_EXT, sanitizedName); } private static Schema getSchemaWithBallerinaNameExtension(String propertyName, Schema propertySchema) { @@ -337,7 +342,7 @@ private static Schema getSchemaWithBallerinaNameExtension(String propertyName, S if (Objects.isNull(propertySchema.getExtensions())) { propertySchema.setExtensions(new HashMap<>()); } - propertySchema.getExtensions().put("x-ballerina-name", sanitizedPropertyName); + propertySchema.getExtensions().put(BALLERINA_NAME_EXT, sanitizedPropertyName); return propertySchema; } @@ -362,7 +367,7 @@ private record PathDetails(PathItem pathItem, String pathValue) { private static void updateObjectPropertyRef(Map properties, String schemaName, String modifiedName) { - if (properties != null) { + if (Objects.nonNull(properties)) { for (Map.Entry property : properties.entrySet()) { Schema fieldValue = property.getValue(); updateSchemaWithReference(schemaName, modifiedName, fieldValue); @@ -380,7 +385,7 @@ private static void updateObjectPropertyRef(Map properties, Stri */ private static void updateSchemaWithReference(String schemaName, String modifiedName, Schema typeSchema) { String ref = typeSchema.get$ref(); - if (ref != null) { + if (Objects.nonNull(ref)) { updateRef(schemaName, modifiedName, typeSchema); } else if (typeSchema instanceof ObjectSchema objectSchema) { Map objectSchemaProperties = objectSchema.getProperties(); @@ -390,10 +395,10 @@ private static void updateSchemaWithReference(String schemaName, String modified updateSchemaWithReference(schemaName, modifiedName, items); } else if (typeSchema instanceof MapSchema mapSchema) { updateObjectPropertyRef(mapSchema.getProperties(), schemaName, modifiedName); - } else if (typeSchema.getProperties() != null) { + } else if (Objects.nonNull(typeSchema.getProperties())) { updateObjectPropertyRef(typeSchema.getProperties(), schemaName, modifiedName); } else if (typeSchema instanceof ComposedSchema composedSchema) { - if (composedSchema.getAllOf() != null) { + if (Objects.nonNull(composedSchema.getAllOf())) { List allOf = composedSchema.getAllOf(); List modifiedAllOf = new ArrayList<>(); for (Schema schema : allOf) { @@ -402,7 +407,7 @@ private static void updateSchemaWithReference(String schemaName, String modified } composedSchema.setAllOf(modifiedAllOf); } - if (composedSchema.getOneOf() != null) { + if (Objects.nonNull(composedSchema.getOneOf())) { List oneOf = composedSchema.getOneOf(); List modifiedOneOf = new ArrayList<>(); for (Schema schema : oneOf) { @@ -411,7 +416,7 @@ private static void updateSchemaWithReference(String schemaName, String modified } composedSchema.setOneOf(modifiedOneOf); } - if (composedSchema.getAnyOf() != null) { + if (Objects.nonNull(composedSchema.getAnyOf())) { List anyOf = composedSchema.getAnyOf(); List modifiedAnyOf = new ArrayList<>(); for (Schema schema : anyOf) { @@ -421,7 +426,7 @@ private static void updateSchemaWithReference(String schemaName, String modified composedSchema.setAnyOf(modifiedAnyOf); } } - if (typeSchema.getAdditionalProperties() != null && + if (Objects.nonNull(typeSchema.getAdditionalProperties()) && typeSchema.getAdditionalProperties() instanceof Schema addtionalSchema) { updateSchemaWithReference(schemaName, modifiedName, addtionalSchema); } From 77c3c2c9bbbaf7b2b9357b04231f650fc5e8ab02 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 08:47:22 +0530 Subject: [PATCH 07/15] Fix test cases with the generated name change --- .../cmd/sanitize/sanitized_openapi_composed_schema.json | 2 +- .../test/resources/cmd/sanitize/sanitized_openapi_expected.json | 2 +- .../test/resources/cmd/sanitize/sanitized_openapi_expected.yaml | 2 +- .../resources/cmd/sanitize/sanitized_openapi_expected_1.json | 2 +- .../resources/cmd/sanitize/sanitized_openapi_expected_1.yaml | 2 +- .../cmd/sanitize/sanitized_openapi_expected_albums.json | 2 +- .../cmd/sanitize/sanitized_openapi_expected_operations.json | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json index 3d9ec87b7..0f28589f5 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_composed_schema.json @@ -50,7 +50,7 @@ "type": "string", "default": "v1" }, - "x-ballerina-name": "xApiVersion" + "x-ballerina-name": "xAPIVERSION" } ], "responses": { diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json index 7f538988d..4c3baab74 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.json @@ -50,7 +50,7 @@ "type": "string", "default": "v1" }, - "x-ballerina-name": "xApiVersion" + "x-ballerina-name": "xAPIVERSION" } ], "responses": { diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml index e5d36eb9c..27ed58694 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected.yaml @@ -35,7 +35,7 @@ paths: schema: type: string default: v1 - x-ballerina-name: xApiVersion + x-ballerina-name: xAPIVERSION responses: "200": description: Ok diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json index fafc3054a..170e3af76 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.json @@ -44,7 +44,7 @@ "type" : "string", "default" : "v1" }, - "x-ballerina-name" : "xApiVersion" + "x-ballerina-name" : "xAPIVERSION" } ], "responses" : { "200" : { diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml index 3117d23e0..a4a698f00 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_1.yaml @@ -35,7 +35,7 @@ paths: schema: type: string default: v1 - x-ballerina-name: xApiVersion + x-ballerina-name: xAPIVERSION responses: "200": description: Ok diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json index 1dbe56916..f59b51cdf 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_albums.json @@ -50,7 +50,7 @@ "type": "string", "default": "v1" }, - "x-ballerina-name": "xApiVersion" + "x-ballerina-name": "xAPIVERSION" } ], "responses": { diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json index 4f5d84be7..e5af9da40 100644 --- a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_expected_operations.json @@ -50,7 +50,7 @@ "type": "string", "default": "v1" }, - "x-ballerina-name": "xApiVersion" + "x-ballerina-name": "xAPIVERSION" } ], "responses": { From 0c67edd6cfb2855ca539beb0e06a9300d88d3fb8 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 12:15:54 +0530 Subject: [PATCH 08/15] Refactor code by utilizing common functions --- .../ballerina/openapi/cmd/CmdConstants.java | 2 - .../io/ballerina/openapi/cmd/Flatten.java | 264 +------------- .../io/ballerina/openapi/cmd/Sanitize.java | 270 +------------- .../io/ballerina/openapi/cmd/SubCmdBase.java | 331 ++++++++++++++++++ 4 files changed, 359 insertions(+), 508 deletions(-) create mode 100644 openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java index f0a50baf8..d68e35b0d 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/CmdConstants.java @@ -206,8 +206,6 @@ public String getValue() { List.of("2.0", "3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.1.0"); public static final String DEFAULT_CLIENT_ID = "oas_%s_%s"; public static final String OPENAPI_ADD_CMD = "add"; - public static final String OPENAPI_FLATTEN_CMD = "flatten"; - public static final String OPENAPI_SANITIZE_CMD = "sanitize"; public enum Mode { SERVICE, diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Flatten.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Flatten.java index d3d17d04c..2d5394ef4 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Flatten.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Flatten.java @@ -17,28 +17,15 @@ */ package io.ballerina.openapi.cmd; -import io.ballerina.cli.BLauncherCmd; import io.ballerina.openapi.core.generators.common.OASModifier; -import io.ballerina.openapi.core.generators.common.model.Filter; -import io.ballerina.openapi.service.mapper.utils.CodegenUtils; -import io.swagger.parser.OpenAPIParser; -import io.swagger.v3.core.util.Json; -import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.Schema; -import io.swagger.v3.parser.core.models.ParseOptions; import io.swagger.v3.parser.core.models.SwaggerParseResult; -import io.swagger.v3.parser.util.InlineModelResolver; import picocli.CommandLine; -import java.io.IOException; import java.io.PrintStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -46,14 +33,8 @@ import java.util.Objects; import java.util.Optional; -import static io.ballerina.openapi.cmd.CmdConstants.JSON_EXTENSION; -import static io.ballerina.openapi.cmd.CmdConstants.OPENAPI_FLATTEN_CMD; -import static io.ballerina.openapi.cmd.CmdConstants.YAML_EXTENSION; -import static io.ballerina.openapi.cmd.CmdConstants.YML_EXTENSION; -import static io.ballerina.openapi.core.generators.common.GeneratorConstants.UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE; import static io.ballerina.openapi.core.generators.common.OASModifier.getResolvedNameMapping; import static io.ballerina.openapi.core.generators.common.OASModifier.getValidNameForType; -import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName; /** * Main class to implement "flatten" subcommand which is used to flatten the OpenAPI definition @@ -66,213 +47,41 @@ description = "Flatten the OpenAPI definition by moving all the inline schemas to the " + "\"#/components/schemas\" section." ) -public class Flatten implements BLauncherCmd { - private static final String COMMAND_IDENTIFIER = "openapi-flatten"; - private static final String COMMA = ","; +public class Flatten extends SubCmdBase { - private static final String INFO_OUTPUT_WRITTEN_MSG = "INFO: flattened OpenAPI definition file was successfully" + - " written to: %s%n"; - private static final String WARNING_INVALID_OUTPUT_FORMAT = "WARNING: invalid output format. The output format" + - " should be either \"json\" or \"yaml\".Defaulting to format of the input file."; - private static final String ERROR_INPUT_PATH_IS_REQUIRED = "ERROR: an OpenAPI definition path is required to " + - "flatten the OpenAPI definition."; - private static final String ERROR_INVALID_INPUT_FILE_EXTENSION = "ERROR: invalid input OpenAPI definition file " + - "extension. The OpenAPI definition file should be in YAML or JSON format."; - private static final String ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE = "ERROR: error occurred while reading " + - "the OpenAPI definition file."; - private static final String ERROR_UNSUPPORTED_OPENAPI_VERSION = "ERROR: provided OpenAPI contract version is " + - "not supported in the tool. Use OpenAPI specification version 2 or higher"; - private static final String ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE = "ERROR: error occurred while " + - "parsing the OpenAPI definition file."; - private static final String FOUND_PARSER_DIAGNOSTICS = "found the following parser diagnostic messages:"; - private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "ERROR: error occurred while " + - "writing the flattened OpenAPI definition file"; private static final String ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES = "ERROR: error occurred while " + "generating schema names"; - private PrintStream infoStream = System.out; - private PrintStream errorStream = System.err; - - private Path targetPath = Paths.get(System.getProperty("user.dir")); - private boolean exitWhenFinish = true; - - @CommandLine.Option(names = {"-h", "--help"}, hidden = true) - public boolean helpFlag; - - @CommandLine.Option(names = {"-i", "--input"}, description = "OpenAPI definition file path.") - public String inputPath; - - @CommandLine.Option(names = {"-o", "--output"}, description = "Location of the flattened OpenAPI definition.") - private String outputPath; - - @CommandLine.Option(names = {"-n", "--name"}, description = "Name of the flattened OpenAPI definition file.") - private String fileName; - - @CommandLine.Option(names = {"-f", "--format"}, description = "Output format of the flattened OpenAPI definition.") - private String format; - - @CommandLine.Option(names = {"-t", "--tags"}, description = "Tags that need to be considered when flattening.") - public String tags; - - @CommandLine.Option(names = {"--operations"}, description = "Operations that need to be included when flattening.") - public String operations; - public Flatten() { + super(CommandType.FLATTEN); } public Flatten(PrintStream errorStream, boolean exitWhenFinish) { - this.errorStream = errorStream; - this.exitWhenFinish = exitWhenFinish; + super(CommandType.FLATTEN, errorStream, exitWhenFinish); } @Override - public void execute() { - if (helpFlag) { - String commandUsageInfo = BLauncherCmd.getCommandUsageInfo(COMMAND_IDENTIFIER); - infoStream.println(commandUsageInfo); - return; - } - - if (Objects.isNull(inputPath) || inputPath.isBlank()) { - errorStream.println(ERROR_INPUT_PATH_IS_REQUIRED); - exitError(); - return; - } - - if (inputPath.endsWith(YAML_EXTENSION) || inputPath.endsWith(JSON_EXTENSION) || - inputPath.endsWith(YML_EXTENSION)) { - populateInputOptions(); - generateFlattenOpenAPI(); - return; - } - - errorStream.println(ERROR_INVALID_INPUT_FILE_EXTENSION); - exitError(); + public String getDefaultFileName() { + return "flattened_openapi"; } - private void populateInputOptions() { - if (Objects.nonNull(format)) { - if (!format.equalsIgnoreCase("json") && !format.equalsIgnoreCase("yaml")) { - setDefaultFormat(); - errorStream.println(WARNING_INVALID_OUTPUT_FORMAT); - } - } else { - setDefaultFormat(); - } - - if (Objects.isNull(fileName)) { - fileName = "flattened_openapi"; - } - - if (Objects.nonNull(outputPath)) { - targetPath = Paths.get(outputPath).isAbsolute() ? - Paths.get(outputPath) : Paths.get(targetPath.toString(), outputPath); - } - } - - private void setDefaultFormat() { - format = inputPath.endsWith(JSON_EXTENSION) ? "json" : "yaml"; - } - - private void generateFlattenOpenAPI() { - String openAPIFileContent; - try { - openAPIFileContent = Files.readString(Path.of(inputPath)); - } catch (Exception e) { - errorStream.println(ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE); - exitError(); - return; - } - - Optional openAPIOptional = getFlattenOpenAPI(openAPIFileContent); - if (openAPIOptional.isEmpty()) { - exitError(); - return; - } - writeFlattenOpenAPIFile(openAPIOptional.get()); - } - - private Optional getFlattenOpenAPI(String openAPIFileContent) { - // Read the contents of the file with default parser options - // Flattening will be done after filtering the operations - SwaggerParseResult parserResult = new OpenAPIParser().readContents(openAPIFileContent, null, - new ParseOptions()); - if (!parserResult.getMessages().isEmpty() && - parserResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) { - errorStream.println(ERROR_UNSUPPORTED_OPENAPI_VERSION); - return Optional.empty(); - } - - OpenAPI openAPI = parserResult.getOpenAPI(); - if (Objects.isNull(openAPI)) { - errorStream.println(ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE); - if (!parserResult.getMessages().isEmpty()) { - errorStream.println(FOUND_PARSER_DIAGNOSTICS); - parserResult.getMessages().forEach(errorStream::println); - } + @Override + public Optional generate(String openAPIFileContent) { + Optional filteredOpenAPI = getFilteredOpenAPI(openAPIFileContent); + if (filteredOpenAPI.isEmpty()) { return Optional.empty(); } + OpenAPI openAPI = filteredOpenAPI.get(); Components components = openAPI.getComponents(); List existingComponentNames = Objects.nonNull(components) && Objects.nonNull(components.getSchemas()) ? new ArrayList<>(components.getSchemas().keySet()) : new ArrayList<>(); - filterOpenAPIOperations(openAPI); - - // Flatten the OpenAPI definition with `flattenComposedSchemas: true` and `camelCaseFlattenNaming: true` - InlineModelResolver inlineModelResolver = new InlineModelResolver(true, true); - inlineModelResolver.flatten(openAPI); - - return sanitizeOpenAPI(openAPI, existingComponentNames); - } - - private void writeFlattenOpenAPIFile(OpenAPI openAPI) { - String outputFileNameWithExt = getOutputFileName(); - try { - CodegenUtils.writeFile(targetPath.resolve(outputFileNameWithExt), - outputFileNameWithExt.endsWith(JSON_EXTENSION) ? Json.pretty(openAPI) : Yaml.pretty(openAPI)); - infoStream.printf(INFO_OUTPUT_WRITTEN_MSG, targetPath.resolve(outputFileNameWithExt)); - } catch (IOException exception) { - errorStream.println(ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE); - exitError(); - } + return getFlattenOpenAPI(openAPI) + .flatMap(flattenOpenAPI -> sanitizeGeneratedFlattenNames(flattenOpenAPI, existingComponentNames)); } - private String getOutputFileName() { - return resolveContractFileName(targetPath, fileName + getFileExtension(), format.equals("json")); - } - - private String getFileExtension() { - return (Objects.nonNull(format) && format.equals("json")) ? JSON_EXTENSION : YAML_EXTENSION; - } - - private void filterOpenAPIOperations(OpenAPI openAPI) { - Filter filter = getFilter(); - if (filter.getOperations().isEmpty() && filter.getTags().isEmpty()) { - return; - } - - // Remove the operations which are not present in the filter - openAPI.getPaths().forEach((path, pathItem) -> pathItem.readOperationsMap() - .forEach((httpMethod, operation) -> { - if (!filter.getOperations().contains(operation.getOperationId()) && - operation.getTags().stream().noneMatch(filter.getTags()::contains)) { - pathItem.operation(httpMethod, null); - } - }) - ); - - // Remove the paths which do not have any operations after filtering - List pathsToRemove = new ArrayList<>(); - openAPI.getPaths().forEach((path, pathItem) -> { - if (pathItem.readOperationsMap().isEmpty()) { - pathsToRemove.add(path); - } - }); - pathsToRemove.forEach(openAPI.getPaths()::remove); - } - - private Optional sanitizeOpenAPI(OpenAPI openAPI, List existingComponentNames) { + private Optional sanitizeGeneratedFlattenNames(OpenAPI openAPI, List existingComponentNames) { Map proposedNameMapping = getProposedNameMapping(openAPI, existingComponentNames); if (proposedNameMapping.isEmpty()) { return Optional.of(openAPI); @@ -281,10 +90,10 @@ private Optional sanitizeOpenAPI(OpenAPI openAPI, List existing SwaggerParseResult parserResult = OASModifier.getOASWithSchemaNameModification(openAPI, proposedNameMapping); openAPI = parserResult.getOpenAPI(); if (Objects.isNull(openAPI)) { - errorStream.println(ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES); + printError(ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES); if (!parserResult.getMessages().isEmpty()) { - errorStream.println(FOUND_PARSER_DIAGNOSTICS); - parserResult.getMessages().forEach(errorStream::println); + printError(FOUND_PARSER_DIAGNOSTICS); + parserResult.getMessages().forEach(this::printError); } return Optional.empty(); } @@ -315,45 +124,4 @@ public Map getProposedNameMapping(OpenAPI openapi, List } return getResolvedNameMapping(nameMap); } - - private Filter getFilter() { - List tagList = new ArrayList<>(); - List operationList = new ArrayList<>(); - - if (Objects.nonNull(tags) && !tags.isEmpty()) { - tagList.addAll(Arrays.asList(tags.split(COMMA))); - } - - if (Objects.nonNull(operations) && !operations.isEmpty()) { - operationList.addAll(Arrays.asList(operations.split(COMMA))); - } - - return new Filter(tagList, operationList); - } - - @Override - public String getName() { - return OPENAPI_FLATTEN_CMD; - } - - @Override - public void printLongDesc(StringBuilder stringBuilder) { - //This is the long description of the command and all handle within help command - } - - @Override - public void printUsage(StringBuilder stringBuilder) { - //This is the usage description of the command and all handle within help command - } - - @Override - public void setParentCmdParser(CommandLine commandLine) { - //This is not used in this command - } - - private void exitError() { - if (exitWhenFinish) { - Runtime.getRuntime().exit(1); - } - } } diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java index ef6cf091f..a4c7dc412 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java @@ -17,38 +17,14 @@ */ package io.ballerina.openapi.cmd; -import io.ballerina.cli.BLauncherCmd; import io.ballerina.openapi.core.generators.common.OASModifier; import io.ballerina.openapi.core.generators.common.exception.BallerinaOpenApiException; -import io.ballerina.openapi.core.generators.common.model.Filter; -import io.ballerina.openapi.service.mapper.utils.CodegenUtils; -import io.swagger.parser.OpenAPIParser; -import io.swagger.v3.core.util.Json; -import io.swagger.v3.core.util.Yaml; import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.parser.core.models.ParseOptions; -import io.swagger.v3.parser.core.models.SwaggerParseResult; -import io.swagger.v3.parser.util.InlineModelResolver; import picocli.CommandLine; -import java.io.IOException; import java.io.PrintStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; import java.util.Optional; -import static io.ballerina.openapi.cmd.CmdConstants.JSON_EXTENSION; -import static io.ballerina.openapi.cmd.CmdConstants.OPENAPI_SANITIZE_CMD; -import static io.ballerina.openapi.cmd.CmdConstants.YAML_EXTENSION; -import static io.ballerina.openapi.cmd.CmdConstants.YML_EXTENSION; -import static io.ballerina.openapi.core.generators.common.GeneratorConstants.UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE; -import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName; - /** * Main class to implement "sanitize" subcommand which is used to flatten and sanitize the OpenAPI definition * by generating Ballerina friendly type schema names and Ballerina type name extensions. @@ -57,209 +33,28 @@ */ @CommandLine.Command( name = "sanitize", - description = "" + description = "Sanitize the OpenAPI definition by generating Ballerina friendly type schema names and " + + "Ballerina type name extensions." ) -public class Sanitize implements BLauncherCmd { - private static final String COMMAND_IDENTIFIER = "openapi-sanitize"; - private static final String COMMA = ","; - - private static final String INFO_OUTPUT_WRITTEN_MSG = "INFO: sanitized OpenAPI definition file was successfully" + - " written to: %s%n"; - private static final String WARNING_INVALID_OUTPUT_FORMAT = "WARNING: invalid output format. The output format" + - " should be either \"json\" or \"yaml\".Defaulting to format of the input file."; - private static final String ERROR_INPUT_PATH_IS_REQUIRED = "ERROR: an OpenAPI definition path is required to " + - "sanitize the OpenAPI definition."; - private static final String ERROR_INVALID_INPUT_FILE_EXTENSION = "ERROR: invalid input OpenAPI definition file " + - "extension. The OpenAPI definition file should be in YAML or JSON format."; - private static final String ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE = "ERROR: error occurred while reading " + - "the OpenAPI definition file."; - private static final String ERROR_UNSUPPORTED_OPENAPI_VERSION = "ERROR: provided OpenAPI contract version is " + - "not supported in the tool. Use OpenAPI specification version 2 or higher"; - private static final String ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE = "ERROR: error occurred while " + - "parsing the OpenAPI definition file."; - private static final String FOUND_PARSER_DIAGNOSTICS = "found the following parser diagnostic messages:"; - private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "ERROR: error occurred while " + - "writing the sanitized OpenAPI definition file"; - private static final String ERROR_OCCURRED_WHILE_GENERATING_SCHEMA_NAMES = "ERROR: error occurred while " + - "generating schema names"; - private static final String DEFAULT_FILE_NAME = "sanitized_openapi"; - - private PrintStream infoStream = System.out; - private PrintStream errorStream = System.err; - - private Path targetPath = Paths.get(System.getProperty("user.dir")); - private boolean exitWhenFinish = true; - - @CommandLine.Option(names = {"-h", "--help"}, hidden = true) - public boolean helpFlag; - - @CommandLine.Option(names = {"-i", "--input"}, description = "OpenAPI definition file path.") - public String inputPath; - - @CommandLine.Option(names = {"-o", "--output"}, description = "Location of the sanitized OpenAPI definition.") - private String outputPath; - - @CommandLine.Option(names = {"-n", "--name"}, description = "Name of the sanitized OpenAPI definition file.") - private String fileName; - - @CommandLine.Option(names = {"-f", "--format"}, description = "Output format of the sanitized OpenAPI definition.") - private String format; - - @CommandLine.Option(names = {"-t", "--tags"}, description = "Tags that need to be considered when sanitizing.") - public String tags; - - @CommandLine.Option(names = {"--operations"}, description = "Operations that need to be included when sanitizing.") - public String operations; +public class Sanitize extends SubCmdBase { public Sanitize() { + super(CommandType.SANITIZE); } public Sanitize(PrintStream errorStream, boolean exitWhenFinish) { - this.errorStream = errorStream; - this.exitWhenFinish = exitWhenFinish; + super(CommandType.SANITIZE, errorStream, exitWhenFinish); } @Override - public void execute() { - if (helpFlag) { - String commandUsageInfo = BLauncherCmd.getCommandUsageInfo(COMMAND_IDENTIFIER); - infoStream.println(commandUsageInfo); - return; - } - - if (Objects.isNull(inputPath) || inputPath.isBlank()) { - errorStream.println(ERROR_INPUT_PATH_IS_REQUIRED); - exitError(); - return; - } - - if (inputPath.endsWith(YAML_EXTENSION) || inputPath.endsWith(JSON_EXTENSION) || - inputPath.endsWith(YML_EXTENSION)) { - populateInputOptions(); - generateSanitizedOpenAPI(); - return; - } - - errorStream.println(ERROR_INVALID_INPUT_FILE_EXTENSION); - exitError(); - } - - private void populateInputOptions() { - if (Objects.nonNull(format)) { - if (!format.equalsIgnoreCase("json") && !format.equalsIgnoreCase("yaml")) { - setDefaultFormat(); - errorStream.println(WARNING_INVALID_OUTPUT_FORMAT); - } - } else { - setDefaultFormat(); - } - - if (Objects.isNull(fileName)) { - fileName = DEFAULT_FILE_NAME; - } - - if (Objects.nonNull(outputPath)) { - targetPath = Paths.get(outputPath).isAbsolute() ? - Paths.get(outputPath) : Paths.get(targetPath.toString(), outputPath); - } - } - - private void setDefaultFormat() { - format = inputPath.endsWith(JSON_EXTENSION) ? "json" : "yaml"; - } - - private void generateSanitizedOpenAPI() { - String openAPIFileContent; - try { - openAPIFileContent = Files.readString(Path.of(inputPath)); - } catch (Exception e) { - errorStream.println(ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE); - exitError(); - return; - } - - Optional openAPIOptional = getSanitizedOpenAPI(openAPIFileContent); - if (openAPIOptional.isEmpty()) { - exitError(); - return; - } - writeSanitizedOpenAPIFile(openAPIOptional.get()); - } - - private Optional getSanitizedOpenAPI(String openAPIFileContent) { - // Read the contents of the file with default parser options - // Sanitizing will be done after filtering the operations - SwaggerParseResult parserResult = new OpenAPIParser().readContents(openAPIFileContent, null, - new ParseOptions()); - if (!parserResult.getMessages().isEmpty() && - parserResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) { - errorStream.println(ERROR_UNSUPPORTED_OPENAPI_VERSION); - return Optional.empty(); - } - - OpenAPI openAPI = parserResult.getOpenAPI(); - if (Objects.isNull(openAPI)) { - errorStream.println(ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE); - if (!parserResult.getMessages().isEmpty()) { - errorStream.println(FOUND_PARSER_DIAGNOSTICS); - parserResult.getMessages().forEach(errorStream::println); - } - return Optional.empty(); - } - - filterOpenAPIOperations(openAPI); - - // Flatten the OpenAPI definition with `flattenComposedSchemas: true` and `camelCaseFlattenNaming: true` - InlineModelResolver inlineModelResolver = new InlineModelResolver(true, true); - inlineModelResolver.flatten(openAPI); - - return sanitizeOpenAPI(openAPI); - } - - private void writeSanitizedOpenAPIFile(OpenAPI openAPI) { - String outputFileNameWithExt = getOutputFileName(); - try { - CodegenUtils.writeFile(targetPath.resolve(outputFileNameWithExt), - outputFileNameWithExt.endsWith(JSON_EXTENSION) ? Json.pretty(openAPI) : Yaml.pretty(openAPI)); - infoStream.printf(INFO_OUTPUT_WRITTEN_MSG, targetPath.resolve(outputFileNameWithExt)); - } catch (IOException exception) { - errorStream.println(ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE); - exitError(); - } - } - - private String getOutputFileName() { - return resolveContractFileName(targetPath, fileName + getFileExtension(), format.equals("json")); + public String getDefaultFileName() { + return "sanitized_openapi"; } - private String getFileExtension() { - return (Objects.nonNull(format) && format.equals("json")) ? JSON_EXTENSION : YAML_EXTENSION; - } - - private void filterOpenAPIOperations(OpenAPI openAPI) { - Filter filter = getFilter(); - if (filter.getOperations().isEmpty() && filter.getTags().isEmpty()) { - return; - } - - // Remove the operations which are not present in the filter - openAPI.getPaths().forEach((path, pathItem) -> pathItem.readOperationsMap() - .forEach((httpMethod, operation) -> { - if (!filter.getOperations().contains(operation.getOperationId()) && - operation.getTags().stream().noneMatch(filter.getTags()::contains)) { - pathItem.operation(httpMethod, null); - } - }) - ); - - // Remove the paths which do not have any operations after filtering - List pathsToRemove = new ArrayList<>(); - openAPI.getPaths().forEach((path, pathItem) -> { - if (pathItem.readOperationsMap().isEmpty()) { - pathsToRemove.add(path); - } - }); - pathsToRemove.forEach(openAPI.getPaths()::remove); + @Override + public Optional generate(String openAPIFileContent) { + Optional filteredOpenAPI = getFilteredOpenAPI(openAPIFileContent); + return filteredOpenAPI.flatMap(this::getFlattenOpenAPI).flatMap(this::sanitizeOpenAPI); } private Optional sanitizeOpenAPI(OpenAPI openAPI) { @@ -267,49 +62,8 @@ private Optional sanitizeOpenAPI(OpenAPI openAPI) { try { return Optional.of(oasSanitizer.modifyWithBallerinaConventions(openAPI)); } catch (BallerinaOpenApiException exp) { - errorStream.printf("ERROR: %s%n", exp.getMessage()); + printError("ERROR: %s".formatted(exp.getMessage())); return Optional.empty(); } } - - private Filter getFilter() { - List tagList = new ArrayList<>(); - List operationList = new ArrayList<>(); - - if (Objects.nonNull(tags) && !tags.isEmpty()) { - tagList.addAll(Arrays.asList(tags.split(COMMA))); - } - - if (Objects.nonNull(operations) && !operations.isEmpty()) { - operationList.addAll(Arrays.asList(operations.split(COMMA))); - } - - return new Filter(tagList, operationList); - } - - @Override - public String getName() { - return OPENAPI_SANITIZE_CMD; - } - - @Override - public void printLongDesc(StringBuilder stringBuilder) { - //This is the long description of the command and all handle within help command - } - - @Override - public void printUsage(StringBuilder stringBuilder) { - //This is the usage description of the command and all handle within help command - } - - @Override - public void setParentCmdParser(CommandLine commandLine) { - //This is not used in this command - } - - private void exitError() { - if (exitWhenFinish) { - Runtime.getRuntime().exit(1); - } - } } diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java new file mode 100644 index 000000000..583e325e6 --- /dev/null +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package io.ballerina.openapi.cmd; + +import io.ballerina.cli.BLauncherCmd; +import io.ballerina.openapi.core.generators.common.model.Filter; +import io.ballerina.openapi.service.mapper.utils.CodegenUtils; +import io.swagger.parser.OpenAPIParser; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Yaml; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.parser.core.models.ParseOptions; +import io.swagger.v3.parser.core.models.SwaggerParseResult; +import io.swagger.v3.parser.util.InlineModelResolver; +import picocli.CommandLine; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static io.ballerina.openapi.cmd.CmdConstants.JSON_EXTENSION; +import static io.ballerina.openapi.cmd.CmdConstants.YAML_EXTENSION; +import static io.ballerina.openapi.cmd.CmdConstants.YML_EXTENSION; +import static io.ballerina.openapi.core.generators.common.GeneratorConstants.UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE; +import static io.ballerina.openapi.service.mapper.utils.CodegenUtils.resolveContractFileName; + +/** + * Abstract class representing the {@link Flatten} and {@link Sanitize} sub command + * implementations. + * + * @since 2.2.0 + */ +public abstract class SubCmdBase implements BLauncherCmd{ + + private static final String JSON = "json"; + + public enum CommandType { + FLATTEN("flatten"), + SANITIZE("sanitize"); + + private final String name; + + CommandType(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + private static final String COMMAND_IDENTIFIER = "openapi-%s"; + private static final String COMMA = ","; + + private static final String INFO_OUTPUT_WRITTEN_MSG = "INFO: %sed OpenAPI definition file was successfully" + + " written to: %s%n"; + private static final String WARNING_INVALID_OUTPUT_FORMAT = "WARNING: invalid output format. The output format" + + " should be either \"json\" or \"yaml\".Defaulting to format of the input file"; + private static final String ERROR_INPUT_PATH_IS_REQUIRED = "ERROR: an OpenAPI definition path is required to " + + "%s the OpenAPI definition%n"; + private static final String ERROR_INVALID_INPUT_FILE_EXTENSION = "ERROR: invalid input OpenAPI definition file " + + "extension. The OpenAPI definition file should be in YAML or JSON format"; + private static final String ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE = "ERROR: error occurred while reading " + + "the OpenAPI definition file"; + private static final String ERROR_UNSUPPORTED_OPENAPI_VERSION = "ERROR: provided OpenAPI contract version is " + + "not supported in the tool. Use OpenAPI specification version 2 or higher"; + private static final String ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE = "ERROR: error occurred while " + + "parsing the OpenAPI definition file"; + protected static final String FOUND_PARSER_DIAGNOSTICS = "found the following parser diagnostic messages:"; + private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "ERROR: error occurred while " + + "writing the %sed OpenAPI definition file%n"; + + private final PrintStream infoStream = System.out; + private PrintStream errorStream = System.err; + + private Path targetPath = Paths.get(System.getProperty("user.dir")); + private boolean exitWhenFinish = true; + private final CommandType cmdType; + + @CommandLine.Option(names = {"-h", "--help"}, hidden = true) + public boolean helpFlag; + + @CommandLine.Option(names = {"-i", "--input"}, description = "OpenAPI definition file path.") + public String inputPath; + + @CommandLine.Option(names = {"-o", "--output"}, description = "Location of the sanitized OpenAPI definition.") + private String outputPath; + + @CommandLine.Option(names = {"-n", "--name"}, description = "Name of the sanitized OpenAPI definition file.") + private String fileName; + + @CommandLine.Option(names = {"-f", "--format"}, description = "Output format of the sanitized OpenAPI definition.") + private String format; + + @CommandLine.Option(names = {"-t", "--tags"}, description = "Tags that need to be considered when sanitizing.") + public String tags; + + @CommandLine.Option(names = {"--operations"}, description = "Operations that need to be included when sanitizing.") + public String operations; + + public SubCmdBase(CommandType cmdType) { + this.cmdType = cmdType; + } + + public SubCmdBase(CommandType cmdType, PrintStream errorStream, boolean exitWhenFinish) { + this.cmdType = cmdType; + this.errorStream = errorStream; + this.exitWhenFinish = exitWhenFinish; + } + + public void printHelpText() { + String commandIdentifier = String.format(COMMAND_IDENTIFIER, cmdType.getName()); + String commandUsageInfo = BLauncherCmd.getCommandUsageInfo(commandIdentifier); + infoStream.println(commandUsageInfo); + } + + @Override + public void execute() { + if (helpFlag) { + printHelpText(); + return; + } + + if (Objects.isNull(inputPath) || inputPath.isBlank()) { + errorStream.printf(ERROR_INPUT_PATH_IS_REQUIRED, cmdType.getName()); + exitError(); + return; + } + + if (inputPath.endsWith(YAML_EXTENSION) || inputPath.endsWith(JSON_EXTENSION) || + inputPath.endsWith(YML_EXTENSION)) { + populateInputOptions(); + generateOpenAPI(); + return; + } + + errorStream.println(ERROR_INVALID_INPUT_FILE_EXTENSION); + exitError(); + } + + public void printError(String message){ + errorStream.println(message); + } + + private void generateOpenAPI() { + String openAPIFileContent; + try { + openAPIFileContent = Files.readString(Path.of(inputPath)); + } catch (Exception e) { + errorStream.println(ERROR_OCCURRED_WHILE_READING_THE_INPUT_FILE); + exitError(); + return; + } + + Optional openAPIOptional = generate(openAPIFileContent); + if (openAPIOptional.isEmpty()) { + exitError(); + return; + } + writeGeneratedOpenAPIFile(openAPIOptional.get()); + } + + public abstract Optional generate(String openAPIFileContent); + + public abstract String getDefaultFileName(); + + public Optional getFlattenOpenAPI(OpenAPI openAPI) { + // Flatten the OpenAPI definition with `flattenComposedSchemas: true` and `camelCaseFlattenNaming: true` + InlineModelResolver inlineModelResolver = new InlineModelResolver(true, true); + inlineModelResolver.flatten(openAPI); + return Optional.of(openAPI); + } + + public Optional getFilteredOpenAPI(String openAPIFileContent) { + // Read the contents of the file with default parser options + // Flattening will be done after filtering the operations + SwaggerParseResult parserResult = new OpenAPIParser().readContents(openAPIFileContent, null, + new ParseOptions()); + if (!parserResult.getMessages().isEmpty() && + parserResult.getMessages().contains(UNSUPPORTED_OPENAPI_VERSION_PARSER_MESSAGE)) { + errorStream.println(ERROR_UNSUPPORTED_OPENAPI_VERSION); + return Optional.empty(); + } + + OpenAPI openAPI = parserResult.getOpenAPI(); + if (Objects.isNull(openAPI)) { + errorStream.println(ERROR_OCCURRED_WHILE_PARSING_THE_INPUT_OPENAPI_FILE); + if (!parserResult.getMessages().isEmpty()) { + errorStream.println(FOUND_PARSER_DIAGNOSTICS); + parserResult.getMessages().forEach(errorStream::println); + } + return Optional.empty(); + } + + filterOpenAPIOperations(openAPI); + return Optional.of(openAPI); + } + + @Override + public String getName() { + return cmdType.getName(); + } + + @Override + public void printLongDesc(StringBuilder stringBuilder) { + //This is the long description of the command and all handle within help command + } + + @Override + public void printUsage(StringBuilder stringBuilder) { + //This is the usage description of the command and all handle within help command + } + + @Override + public void setParentCmdParser(CommandLine commandLine) { + //This is not used in this command + } + + private void populateInputOptions() { + if (Objects.nonNull(format)) { + if (!format.equalsIgnoreCase(JSON) && !format.equalsIgnoreCase("yaml")) { + setDefaultFormat(); + errorStream.println(WARNING_INVALID_OUTPUT_FORMAT); + } + } else { + setDefaultFormat(); + } + + if (Objects.isNull(fileName)) { + fileName = String.format(getDefaultFileName(), cmdType.getName()); + } + + if (Objects.nonNull(outputPath)) { + targetPath = Paths.get(outputPath).isAbsolute() ? + Paths.get(outputPath) : Paths.get(targetPath.toString(), outputPath); + } + } + + private void setDefaultFormat() { + format = inputPath.endsWith(JSON_EXTENSION) ? JSON : "yaml"; + } + + private void exitError() { + if (exitWhenFinish) { + Runtime.getRuntime().exit(1); + } + } + + private void writeGeneratedOpenAPIFile(OpenAPI openAPI) { + String outputFileNameWithExt = getOutputFileName(); + try { + CodegenUtils.writeFile(targetPath.resolve(outputFileNameWithExt), + outputFileNameWithExt.endsWith(JSON_EXTENSION) ? Json.pretty(openAPI) : Yaml.pretty(openAPI)); + infoStream.printf(INFO_OUTPUT_WRITTEN_MSG, cmdType.getName(), targetPath.resolve(outputFileNameWithExt)); + } catch (IOException exception) { + errorStream.printf(ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE, cmdType.getName()); + exitError(); + } + } + + private String getOutputFileName() { + return resolveContractFileName(targetPath, fileName + getFileExtension(), format.equals(JSON)); + } + + private String getFileExtension() { + return (Objects.nonNull(format) && format.equals(JSON)) ? JSON_EXTENSION : YAML_EXTENSION; + } + + private Filter getFilter() { + List tagList = new ArrayList<>(); + List operationList = new ArrayList<>(); + + if (Objects.nonNull(tags) && !tags.isEmpty()) { + tagList.addAll(Arrays.asList(tags.split(COMMA))); + } + + if (Objects.nonNull(operations) && !operations.isEmpty()) { + operationList.addAll(Arrays.asList(operations.split(COMMA))); + } + + return new Filter(tagList, operationList); + } + + private void filterOpenAPIOperations(OpenAPI openAPI) { + Filter filter = getFilter(); + if (filter.getOperations().isEmpty() && filter.getTags().isEmpty()) { + return; + } + + // Remove the operations which are not present in the filter + openAPI.getPaths().forEach((path, pathItem) -> pathItem.readOperationsMap() + .forEach((httpMethod, operation) -> { + if (!filter.getOperations().contains(operation.getOperationId()) && + operation.getTags().stream().noneMatch(filter.getTags()::contains)) { + pathItem.operation(httpMethod, null); + } + }) + ); + + // Remove the paths which do not have any operations after filtering + List pathsToRemove = new ArrayList<>(); + openAPI.getPaths().forEach((path, pathItem) -> { + if (pathItem.readOperationsMap().isEmpty()) { + pathsToRemove.add(path); + } + }); + pathsToRemove.forEach(openAPI.getPaths()::remove); + } +} From 10d7a491c9e1c5af0f404816dd6ddf576399abb9 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 12:16:06 +0530 Subject: [PATCH 09/15] Fix negative test cases --- .../openapi/cmd/NegativeCmdTests.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java index 042b2bd37..dd691315e 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java @@ -52,7 +52,7 @@ public void testFlattenWithOutInputOpenAPIFile() throws IOException { flatten.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("ERROR: an OpenAPI definition path is required to flatten the OpenAPI " + - "definition.")); + "definition")); } @Test(description = "Test with the invalid input OpenAPI file in `flatten` sub command") @@ -62,7 +62,7 @@ public void testFlattenWithInvalidInputOpenAPIFile() throws IOException { new CommandLine(flatten).parseArgs(args); flatten.execute(); String output = readOutput(true); - Assert.assertTrue(output.contains("ERROR: error occurred while reading the OpenAPI definition file.")); + Assert.assertTrue(output.contains("ERROR: error occurred while reading the OpenAPI definition file")); } @Test(description = "Test with invalid invalid input OpenAPI file extension in `flatten` sub command") @@ -73,7 +73,7 @@ public void testFlattenWithInvalidInputOpenAPIFileExtension() throws IOException flatten.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("ERROR: invalid input OpenAPI definition file extension. The OpenAPI " + - "definition file should be in YAML or JSON format.")); + "definition file should be in YAML or JSON format")); } @Test(description = "Test with the invalid output OpenAPI file format in `flatten` sub command") @@ -84,7 +84,7 @@ public void testFlattenWithInvalidOutputOpenAPIFileFormat() throws IOException { flatten.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + - "\"json\" or \"yaml\".Defaulting to format of the input file.")); + "\"json\" or \"yaml\".Defaulting to format of the input file")); } @Test(description = "Test with the input OpenAPI file in `flatten` sub command which has parsing issues") @@ -95,7 +95,7 @@ public void testFlattenWithInputOpenAPIFileParsingIssues() throws IOException { flatten.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + - "\"json\" or \"yaml\".Defaulting to format of the input file.")); + "\"json\" or \"yaml\".Defaulting to format of the input file")); } @Test(description = "Test without the input OpenAPI file in `sanitize` sub command") @@ -106,7 +106,7 @@ public void testSanitizeWithOutInputOpenAPIFile() throws IOException { sanitize.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("ERROR: an OpenAPI definition path is required to sanitize the OpenAPI " + - "definition.")); + "definition")); } @Test(description = "Test with the invalid input OpenAPI file in `sanitize` sub command") @@ -116,7 +116,7 @@ public void testSanitizeWithInvalidInputOpenAPIFile() throws IOException { new CommandLine(sanitize).parseArgs(args); sanitize.execute(); String output = readOutput(true); - Assert.assertTrue(output.contains("ERROR: error occurred while reading the OpenAPI definition file.")); + Assert.assertTrue(output.contains("ERROR: error occurred while reading the OpenAPI definition file")); } @Test(description = "Test with invalid invalid input OpenAPI file extension in `sanitize` sub command") @@ -127,7 +127,7 @@ public void testSanitizeWithInvalidInputOpenAPIFileExtension() throws IOExceptio sanitize.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("ERROR: invalid input OpenAPI definition file extension. The OpenAPI " + - "definition file should be in YAML or JSON format.")); + "definition file should be in YAML or JSON format")); } @Test(description = "Test with the invalid output OpenAPI file format in `sanitize` sub command") @@ -138,7 +138,7 @@ public void testSanitizeWithInvalidOutputOpenAPIFileFormat() throws IOException sanitize.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + - "\"json\" or \"yaml\".Defaulting to format of the input file.")); + "\"json\" or \"yaml\".Defaulting to format of the input file")); } @Test(description = "Test with the input OpenAPI file in `sanitize` sub command which has parsing issues") @@ -150,6 +150,6 @@ public void testSanitizeWithInputOpenAPIFileParsingIssues() throws IOException { sanitize.execute(); String output = readOutput(true); Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + - "\"json\" or \"yaml\".Defaulting to format of the input file.")); + "\"json\" or \"yaml\".Defaulting to format of the input file")); } } From dc3648d748686947d45d2f36ae67cc6f6035cbe0 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 12:59:10 +0530 Subject: [PATCH 10/15] Fix checkstyle issues --- .../src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java index 583e325e6..b15134e00 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -52,7 +52,7 @@ * * @since 2.2.0 */ -public abstract class SubCmdBase implements BLauncherCmd{ +public abstract class SubCmdBase implements BLauncherCmd { private static final String JSON = "json"; @@ -160,7 +160,7 @@ public void execute() { exitError(); } - public void printError(String message){ + public void printError(String message) { errorStream.println(message); } From 6c1a7b95cb23fd54b97378a573673e72dba0e8c7 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 12 Sep 2024 13:55:48 +0530 Subject: [PATCH 11/15] Address sonar cloud suggestions --- .../src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java index b15134e00..ce97eca3a 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -92,7 +92,7 @@ public String getName() { private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "ERROR: error occurred while " + "writing the %sed OpenAPI definition file%n"; - private final PrintStream infoStream = System.out; + private static final PrintStream infoStream = System.out; private PrintStream errorStream = System.err; private Path targetPath = Paths.get(System.getProperty("user.dir")); @@ -120,11 +120,11 @@ public String getName() { @CommandLine.Option(names = {"--operations"}, description = "Operations that need to be included when sanitizing.") public String operations; - public SubCmdBase(CommandType cmdType) { + protected SubCmdBase(CommandType cmdType) { this.cmdType = cmdType; } - public SubCmdBase(CommandType cmdType, PrintStream errorStream, boolean exitWhenFinish) { + protected SubCmdBase(CommandType cmdType, PrintStream errorStream, boolean exitWhenFinish) { this.cmdType = cmdType; this.errorStream = errorStream; this.exitWhenFinish = exitWhenFinish; From 49b5e31e836e1800b9cafc85a7e6d24d393a5f01 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 09:28:29 +0530 Subject: [PATCH 12/15] Add suggestions from review --- .../io/ballerina/openapi/cmd/SubCmdBase.java | 3 +- .../ballerina/openapi/cmd/OpenAPICmdTest.java | 20 ++ .../resources/cmd/sanitize/openapi_2.0.yaml | 174 +++++++++++++++ .../resources/cmd/sanitize/openapi_3.0.0.yaml | 190 ++++++++++++++++ .../sanitized_openapi_2.0_expected.yaml | 198 +++++++++++++++++ .../sanitized_openapi_3.0.0_expected.yaml | 203 ++++++++++++++++++ 6 files changed, 787 insertions(+), 1 deletion(-) create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/openapi_2.0.yaml create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/openapi_3.0.0.yaml create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_2.0_expected.yaml create mode 100644 openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_3.0.0_expected.yaml diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java index ce97eca3a..145bd4e97 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -55,6 +55,7 @@ public abstract class SubCmdBase implements BLauncherCmd { private static final String JSON = "json"; + private static final String YAML = "yaml"; public enum CommandType { FLATTEN("flatten"), @@ -259,7 +260,7 @@ private void populateInputOptions() { } private void setDefaultFormat() { - format = inputPath.endsWith(JSON_EXTENSION) ? JSON : "yaml"; + format = inputPath.endsWith(JSON_EXTENSION) ? JSON : YAML; } private void exitError() { diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java index cdf3b6fb0..2ab10554e 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/OpenAPICmdTest.java @@ -1045,6 +1045,26 @@ public void testSanitizeCmdDefaultYaml() throws IOException { compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.yaml")); } + @Test(description = "Test openapi sanitize sub command with Swagger 2.0") + public void testSanitizeCmdWithSwaggerV2() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_2.0_expected.yaml")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi_2.0.yaml", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.yaml")); + } + + @Test(description = "Test openapi sanitize sub command with OpenAPI 3.0.0") + public void testSanitizeCmdWithOpenAPIV3_0_0() throws IOException { + Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_3.0.0_expected.yaml")); + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi_3.0.0.yaml", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + compareFiles(expectedFilePath, tmpDir.resolve("sanitized_openapi.yaml")); + } + @Test(description = "Test openapi sanitize sub command with the name option") public void testSanitizeCmdName() throws IOException { Path expectedFilePath = resourceDir.resolve(Paths.get("cmd/sanitize/sanitized_openapi_expected.json")); diff --git a/openapi-cli/src/test/resources/cmd/sanitize/openapi_2.0.yaml b/openapi-cli/src/test/resources/cmd/sanitize/openapi_2.0.yaml new file mode 100644 index 000000000..c01edf998 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/openapi_2.0.yaml @@ -0,0 +1,174 @@ +swagger: "2.0" +info: + title: Api V1 + version: 0.0.0 +host: "localhost:8080" +basePath: "/api/v1" +schemes: + - http +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + produces: + - application/json + parameters: + - name: _artists_ + in: query + type: array + items: + type: string + collectionFormat: csv + default: [] + - name: X-API-VERSION + in: header + type: string + default: v1 + responses: + "200": + description: Ok + schema: + type: array + items: + $ref: "#/definitions/album" + "400": + description: BadRequest + schema: + $ref: "#/definitions/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + produces: + - application/json + consumes: + - application/json + parameters: + - in: body + name: body + required: true + schema: + $ref: "#/definitions/album" + responses: + "201": + description: Created + schema: + $ref: "#/definitions/album" + "400": + description: BadRequest + schema: + $ref: "#/definitions/ErrorPayload" + /albums/{_id}: + get: + tags: + - albums + operationId: getAlbumById + produces: + - application/json + parameters: + - name: _id + in: path + required: true + type: string + responses: + "200": + description: Ok + schema: + $ref: "#/definitions/album" + "404": + description: NotFound + schema: + $ref: "#/definitions/message" + "400": + description: BadRequest + schema: + $ref: "#/definitions/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: string + responses: + "200": + description: Ok + schema: + $ref: "#/definitions/album_aRTIST" + "400": + description: BadRequest + schema: + $ref: "#/definitions/ErrorPayload" +definitions: + 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 + album: + required: + - _id + - artist + - title + type: object + properties: + _id: + type: string + title: + type: string + artist: + type: string + additionalProperties: false + album_aRTIST: + required: + - albums + - id + - name + type: object + properties: + id: + type: string + name: + type: string + albums: + type: array + items: + $ref: "#/definitions/album" + additionalProperties: false + message: + required: + - code + - message + type: object + properties: + message: + type: string + code: + type: integer + format: int64 + additionalProperties: false diff --git a/openapi-cli/src/test/resources/cmd/sanitize/openapi_3.0.0.yaml b/openapi-cli/src/test/resources/cmd/sanitize/openapi_3.0.0.yaml new file mode 100644 index 000000000..e634aaabc --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/openapi_3.0.0.yaml @@ -0,0 +1,190 @@ +openapi: 3.0.0 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + schema: + type: array + items: + type: string + default: [] + - name: X-API-VERSION + in: header + schema: + type: string + default: v1 + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/album' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/album' + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: '#/components/schemas/album' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' + /albums/{_id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: _id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/album' + "404": + description: NotFound + content: + application/json: + schema: + $ref: '#/components/schemas/message' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/album_aRTIST' + "400": + description: BadRequest + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorPayload' +components: + schemas: + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + timestamp: + type: string + format: date-time + status: + type: integer + format: int64 + reason: + type: string + message: + type: string + path: + type: string + method: + type: string + album: + required: + - _id\- + - artist + - title + type: object + properties: + _id\-: + type: string + title: + type: string + artist: + type: string + additionalProperties: false + album_aRTIST: + required: + - albums + - id + - name + type: object + properties: + id: + type: string + name: + type: string + albums: + type: array + items: + $ref: "#/components/schemas/album" + additionalProperties: false + message: + required: + - code + - message + type: object + properties: + message: + type: string + code: + type: integer + format: int64 + additionalProperties: false diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_2.0_expected.yaml b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_2.0_expected.yaml new file mode 100644 index 000000000..8d8c74023 --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_2.0_expected.yaml @@ -0,0 +1,198 @@ +openapi: 3.0.1 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: http://localhost:8080/api/v1 +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + required: false + style: form + explode: false + schema: + type: array + items: + type: string + x-ballerina-name: artists + - name: X-API-VERSION + in: header + required: false + style: simple + explode: false + schema: + type: string + default: v1 + x-ballerina-name: xAPIVERSION + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + x-codegen-request-body-name: body + /albums/{id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/Message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/AlbumARTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + AlbumARTIST: + required: + - albums + - id + - name + type: object + properties: + albums: + type: array + items: + $ref: "#/components/schemas/Album" + name: + type: string + id: + type: string + additionalProperties: false + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + reason: + type: string + path: + type: string + method: + type: string + message: + type: string + timestamp: + type: string + status: + type: integer + format: int64 + Message: + required: + - code + - message + type: object + properties: + code: + type: integer + format: int64 + message: + type: string + additionalProperties: false + Album: + required: + - _id + - artist + - title + type: object + properties: + artist: + type: string + _id: + type: string + x-ballerina-name: id + title: + type: string + additionalProperties: false +x-original-swagger-version: "2.0" diff --git a/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_3.0.0_expected.yaml b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_3.0.0_expected.yaml new file mode 100644 index 000000000..14d64a0eb --- /dev/null +++ b/openapi-cli/src/test/resources/cmd/sanitize/sanitized_openapi_3.0.0_expected.yaml @@ -0,0 +1,203 @@ +openapi: 3.0.0 +info: + title: Api V1 + version: 0.0.0 +servers: + - url: "http://{server}:{port}/api/v1" + variables: + server: + default: localhost + port: + default: "8080" +paths: + /albums: + get: + tags: + - albums + operationId: getAlbums + parameters: + - name: _artists_ + in: query + required: false + style: form + explode: true + schema: + type: array + items: + type: string + default: [] + x-ballerina-name: artists + - name: X-API-VERSION + in: header + required: false + style: simple + explode: false + schema: + type: string + default: v1 + x-ballerina-name: xAPIVERSION + responses: + "200": + description: Ok + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + post: + tags: + - albums + operationId: postAlbum + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + required: true + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}: + get: + tags: + - albums + operationId: getAlbumById + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/Album" + "404": + description: NotFound + content: + application/json: + schema: + $ref: "#/components/schemas/Message" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" + /albums/{id}/artist: + get: + tags: + - artists + operationId: getArtistByAlbum + parameters: + - name: id + in: path + required: true + style: simple + explode: false + schema: + type: string + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: "#/components/schemas/AlbumARTIST" + "400": + description: BadRequest + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorPayload" +components: + schemas: + AlbumARTIST: + required: + - albums + - id + - name + type: object + properties: + albums: + type: array + items: + $ref: "#/components/schemas/Album" + name: + type: string + id: + type: string + additionalProperties: false + ErrorPayload: + required: + - message + - method + - path + - reason + - status + - timestamp + type: object + properties: + reason: + type: string + path: + type: string + method: + type: string + message: + type: string + timestamp: + type: string + format: date-time + status: + type: integer + format: int64 + Message: + required: + - code + - message + type: object + properties: + code: + type: integer + format: int64 + message: + type: string + additionalProperties: false + Album: + required: + - _id\- + - artist + - title + type: object + properties: + _id\-: + type: string + x-ballerina-name: id + artist: + type: string + title: + type: string + additionalProperties: false From b9b5782d8c11cdc0e671d8191d4e6ecf421bde4d Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 10:43:46 +0530 Subject: [PATCH 13/15] Fix test failure --- .../project_openapi_bal_ext/result_1.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml index 0dafce369..996094380 100644 --- a/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml +++ b/openapi-integration-tests/src/test/resources/ballerina_sources/project_openapi_bal_ext/result_1.yaml @@ -57,7 +57,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Date DateFields: @@ -81,7 +81,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: DateFields OptionalTimeOfDayFields: @@ -100,7 +100,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: OptionalTimeOfDayFields Seconds: @@ -111,7 +111,7 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: Seconds Student: @@ -165,6 +165,6 @@ components: orgName: ballerina pkgName: time moduleName: time - version: 2.4.0 + version: 2.5.0 modulePrefix: time name: ZoneOffset From a6736d245108946e8175a86ab3f9ec6d21e9f4e6 Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 12:14:40 +0530 Subject: [PATCH 14/15] Add warning for swagger v2 --- .../java/io/ballerina/openapi/cmd/SubCmdBase.java | 7 +++++++ .../io/ballerina/openapi/cmd/NegativeCmdTests.java | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java index 145bd4e97..6a8359c10 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -92,6 +92,8 @@ public String getName() { protected static final String FOUND_PARSER_DIAGNOSTICS = "found the following parser diagnostic messages:"; private static final String ERROR_OCCURRED_WHILE_WRITING_THE_OUTPUT_OPENAPI_FILE = "ERROR: error occurred while " + "writing the %sed OpenAPI definition file%n"; + private static final String WARNING_SWAGGER_V2_FOUND = "WARNING: Swagger version 2.0 found in the OpenAPI " + + "definition. The generated OpenAPI definition will be in OpenAPI version 3.0.x"; private static final PrintStream infoStream = System.out; private PrintStream errorStream = System.err; @@ -215,6 +217,11 @@ public Optional getFilteredOpenAPI(String openAPIFileContent) { return Optional.empty(); } + if (Objects.nonNull(openAPI.getExtensions()) && openAPI.getExtensions().containsKey("x-original-swagger-version") && + openAPI.getExtensions().get("x-original-swagger-version").toString().trim().equals("2.0")) { + errorStream.println(WARNING_SWAGGER_V2_FOUND); + } + filterOpenAPIOperations(openAPI); return Optional.of(openAPI); } diff --git a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java index dd691315e..60522ea60 100644 --- a/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java +++ b/openapi-cli/src/test/java/io/ballerina/openapi/cmd/NegativeCmdTests.java @@ -152,4 +152,15 @@ public void testSanitizeWithInputOpenAPIFileParsingIssues() throws IOException { Assert.assertTrue(output.contains("WARNING: invalid output format. The output format should be either " + "\"json\" or \"yaml\".Defaulting to format of the input file")); } + + @Test(description = "Test with the input OpenAPI file with Swagger V2 in `sanitize` sub command") + public void testSanitizeWithSwaggerV2() throws IOException { + String[] args = {"-i", resourceDir + "/cmd/sanitize/openapi_2.0.yaml", "-o", tmpDir.toString()}; + Sanitize sanitize = new Sanitize(printStream, false); + new CommandLine(sanitize).parseArgs(args); + sanitize.execute(); + String output = readOutput(true); + Assert.assertTrue(output.contains("WARNING: Swagger version 2.0 found in the OpenAPI definition. The " + + "generated OpenAPI definition will be in OpenAPI version 3.0.x")); + } } From 5fb3b303674c61c6558282c85bac6d13af64ed2c Mon Sep 17 00:00:00 2001 From: TharmiganK Date: Thu, 19 Sep 2024 12:53:46 +0530 Subject: [PATCH 15/15] Fix check style issues --- .../main/java/io/ballerina/openapi/cmd/SubCmdBase.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java index 6a8359c10..c5322fd7c 100644 --- a/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -56,6 +56,8 @@ public abstract class SubCmdBase implements BLauncherCmd { private static final String JSON = "json"; private static final String YAML = "yaml"; + public static final String X_ORIGINAL_SWAGGER_VERSION = "x-original-swagger-version"; + public static final String V2 = "2.0"; public enum CommandType { FLATTEN("flatten"), @@ -217,8 +219,7 @@ public Optional getFilteredOpenAPI(String openAPIFileContent) { return Optional.empty(); } - if (Objects.nonNull(openAPI.getExtensions()) && openAPI.getExtensions().containsKey("x-original-swagger-version") && - openAPI.getExtensions().get("x-original-swagger-version").toString().trim().equals("2.0")) { + if (Objects.nonNull(openAPI.getExtensions()) && isSwaggerV2(openAPI)) { errorStream.println(WARNING_SWAGGER_V2_FOUND); } @@ -226,6 +227,11 @@ public Optional getFilteredOpenAPI(String openAPIFileContent) { return Optional.of(openAPI); } + private static boolean isSwaggerV2(OpenAPI openAPI) { + return openAPI.getExtensions().containsKey(X_ORIGINAL_SWAGGER_VERSION) && + openAPI.getExtensions().get(X_ORIGINAL_SWAGGER_VERSION).toString().trim().equals(V2); + } + @Override public String getName() { return cmdType.getName();