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 0cc33f488..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,7 +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 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/OpenApiCmd.java b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/OpenApiCmd.java index 0450ebe23..28af20e0c 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, Flatten.class} + subcommands = {Add.class, Flatten.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..a4c7dc412 --- /dev/null +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/Sanitize.java @@ -0,0 +1,69 @@ +/* + * 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.openapi.core.generators.common.OASModifier; +import io.ballerina.openapi.core.generators.common.exception.BallerinaOpenApiException; +import io.swagger.v3.oas.models.OpenAPI; +import picocli.CommandLine; + +import java.io.PrintStream; +import java.util.Optional; + +/** + * 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 = "Sanitize the OpenAPI definition by generating Ballerina friendly type schema names and " + + "Ballerina type name extensions." +) +public class Sanitize extends SubCmdBase { + + public Sanitize() { + super(CommandType.SANITIZE); + } + + public Sanitize(PrintStream errorStream, boolean exitWhenFinish) { + super(CommandType.SANITIZE, errorStream, exitWhenFinish); + } + + @Override + public String getDefaultFileName() { + return "sanitized_openapi"; + } + + @Override + public Optional generate(String openAPIFileContent) { + Optional filteredOpenAPI = getFilteredOpenAPI(openAPIFileContent); + return filteredOpenAPI.flatMap(this::getFlattenOpenAPI).flatMap(this::sanitizeOpenAPI); + } + + private Optional sanitizeOpenAPI(OpenAPI openAPI) { + OASModifier oasSanitizer = new OASModifier(); + try { + return Optional.of(oasSanitizer.modifyWithBallerinaConventions(openAPI)); + } catch (BallerinaOpenApiException exp) { + printError("ERROR: %s".formatted(exp.getMessage())); + return Optional.empty(); + } + } +} 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..c5322fd7c --- /dev/null +++ b/openapi-cli/src/main/java/io/ballerina/openapi/cmd/SubCmdBase.java @@ -0,0 +1,345 @@ +/* + * 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"; + 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"), + 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 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; + + 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; + + protected SubCmdBase(CommandType cmdType) { + this.cmdType = cmdType; + } + + protected 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(); + } + + if (Objects.nonNull(openAPI.getExtensions()) && isSwaggerV2(openAPI)) { + errorStream.println(WARNING_SWAGGER_V2_FOUND); + } + + filterOpenAPIOperations(openAPI); + 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(); + } + + @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); + } +} 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..d2154a38a --- /dev/null +++ b/openapi-cli/src/main/resources/cli-help/ballerina-openapi-sanitize.help @@ -0,0 +1,58 @@ +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 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 + -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 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 5755f11e2..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 @@ -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,6 +95,72 @@ 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") + 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")); + } + + @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")); } } 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 aa6b2869e..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 @@ -1025,6 +1025,109 @@ public void testFlattenCmdWithComposedSchema() throws IOException { compareFiles(expectedFilePath, tmpDir.resolve("flattened_openapi.yaml")); } + @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")); + } + + @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 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")); + 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_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/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_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 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..0f28589f5 --- /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..4c3baab74 --- /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..27ed58694 --- /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..170e3af76 --- /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..a4a698f00 --- /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..f59b51cdf --- /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..e5af9da40 --- /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 + } + } + } +} 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 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(); } @@ -106,20 +111,42 @@ public static Map getResolvedNameMapping(Map nam 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 (Objects.nonNull(properties) && !properties.isEmpty()) { + objectSchema.setProperties(getPropertiesWithBallerinaNameExtension(properties)); + } + } + } + } + + private static void modifyOASWithParameterName(OpenAPI openapi) { Paths paths = openapi.getPaths(); - if (paths == null || paths.isEmpty()) { - return openapi; + if (Objects.isNull(paths) || paths.isEmpty()) { + 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 { @@ -192,65 +219,65 @@ public static SwaggerParseResult getOASWithSchemaNameModification(OpenAPI openap private static PathDetails updateParameterNameDetails(Map.Entry 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); @@ -262,7 +289,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); @@ -274,19 +301,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 (location.equals(PATH)) { + String modifiedPathParam = parameterNames.get(parameter.getName()); + parameter.setName(modifiedPathParam); + pathValue = replaceContentInBraces(pathValue, modifiedPathParam); + } else if (location.equals(QUERY) || location.equals(HEADER)) { + 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(BALLERINA_NAME_EXT, 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(BALLERINA_NAME_EXT, 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. * @@ -298,7 +373,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); @@ -316,7 +391,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(); @@ -326,10 +401,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) { @@ -338,7 +413,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) { @@ -347,7 +422,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) { @@ -357,7 +432,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); }