diff --git a/docs/customization.md b/docs/customization.md index ffff0188e96d..f93151764aa5 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -655,7 +655,7 @@ java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generat The `FILTER` parameter allows selective inclusion of API operations based on specific criteria. It applies the `x-internal: true` property to operations that do **not** match the specified values, preventing them from being generated. Multiple filters can be separated by a semicolon. -### Available Filters +### Available FILTER filters - **`operationId`** When set to `operationId:addPet|getPetById`, operations **not** matching `addPet` or `getPetById` will be marked as internal (`x-internal: true`), and excluded from generation. Matching operations will have `x-internal: false`. @@ -669,6 +669,9 @@ The `FILTER` parameter allows selective inclusion of API operations based on spe - **`path`** When set to `path:/v1|/v2`, operations on paths **not** starting with `/v1` or with `/v2` will be marked as internal (`x-internal: true`), and will not be generated. +- **`x-`** + When set to `x-role:admin|superuser`, operations or parameters having vendorExtension `x-role` with a value **not** in [`admin`,`superuser`] will be marked as internal (`x-internal: true`), and will not be generated. + ### Example Usage ```sh @@ -723,3 +726,52 @@ Into this securityScheme: scheme: bearer type: http ``` + +- `REMOVE_FILTER` + +The `REMOVE_FILTER` parameter allows the removal of elements in an openAPI document. A semicolon can separate multiple filters. + +### Available REMOVE_FILTER filters + +- **`internal`** + When specified as `internal` or set to `internal:true`, all operations, schemas, properties and parameters marked with `x-internal: true` are removed from the document. Optionally set individual options like in `internal:operations|schemas|properties|parameters` + +- **`deprecated`** + When specified as `deprecated` or set to `deprecated:true`, all operations, schemas, properties and parameters marked with `deprecated: true` are removed. + +- **`removeTags`** + When specified as `removeTags` or set to `removeTags:true`, all tags are removed from the operations. + When set to `removeTags:store|user` all tags named is `store` or `user` are removed. + When set to `removeTags:keepOnlyFirstTag` perform the KEEP_ONLY_FIRST_TAG_IN_OPERATION normalization + +- **`removeVendorExtensions`** + When specified as `removeVendorExtensions` or set to `removeVendorExtensions:true`, remove all vendorExtensions (including x-internal). + When set to `removeVendorExtensions:x-role|x-groups`, remove all `x-role` and `x-groups` vendorExtensions. When set to `removeVendorExtensions:x-internal`, perform the REMOVE_X_INTERNAL normalization. + +- **`x-`** + When specified as `x-role`, remove operations, schemas, properties and parameters having a vendor extension `x-role`. + When set to `x-role:admin|superuser`, remove operations, schemas, properties and parameters marked with vendorExtension `x-role: admin` or `x-role: superuser`. + +- **`tags`** + When set as `tags:user|store`, remove operations marked with tags: user or store. + +- **`headers`** + When set as `headers`, remove all headers. + When set as `headers:x-api-key`, remove header parameters named `x-api-key`. + +- **`cookies`** + When set as `cookies`, remove all cookies. + When set as `cookies:x-api-key`, remove cookie parameters named `x-api-key`. + +- **`queryParams`** + When set as `queryParams:offset|limit`, remove request parameters named `offset` or `limit`.`. + +- **`unused`** + When specified as `unused` or set to `unused:true`, remove all unused schemas, tags, requestBodies, responses and parameters. + Optionally set individual options like in `unused:schemas|tags|requestBodies|responses|parameters` + +Example: +``` +java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g openapi -i modules/openapi-generator/src/test/resources/3_1/java/petstore.yaml -o /tmp/openapi/ --openapi-normalizer FILTER=tag:pet --openapi-normalizer REMOVE_FILTER=internal;unused +``` +generates an openapi.json without the store and user operations. diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java index 64ed0b624d11..8dc0b8033c3a 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java @@ -27,6 +27,7 @@ import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.tags.Tag; import org.apache.commons.lang3.StringUtils; import org.openapitools.codegen.utils.ModelUtils; import org.slf4j.Logger; @@ -34,9 +35,9 @@ import java.lang.reflect.Constructor; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; +import static org.apache.commons.lang3.ObjectUtils.isEmpty; import static org.openapitools.codegen.CodegenConstants.X_INTERNAL; import static org.openapitools.codegen.CodegenConstants.X_PARENT; import static org.openapitools.codegen.utils.ModelUtils.simplifyOneOfAnyOfWithOnlyOneNonNullSubSchema; @@ -68,38 +69,38 @@ public class OpenAPINormalizer { // when set to true, $ref in allOf is treated as parent so that x-parent: true will be added // to the schema in $ref (if x-parent is not present) - final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF"; + static final String REF_AS_PARENT_IN_ALLOF = "REF_AS_PARENT_IN_ALLOF"; // when set to true, only keep the first tag in operation if there are more than one tag defined. - final String KEEP_ONLY_FIRST_TAG_IN_OPERATION = "KEEP_ONLY_FIRST_TAG_IN_OPERATION"; + static final String KEEP_ONLY_FIRST_TAG_IN_OPERATION = "KEEP_ONLY_FIRST_TAG_IN_OPERATION"; // when set to true, complex composed schemas (a mix of oneOf/anyOf/anyOf and properties) with // oneOf/anyOf containing only `required` and no properties (these are properties inter-dependency rules) // are removed as most generators cannot handle such case at the moment - final String REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY = "REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY"; + static final String REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY = "REMOVE_ANYOF_ONEOF_AND_KEEP_PROPERTIES_ONLY"; // when set to true, oneOf/anyOf with either string or enum string as sub schemas will be simplified // to just string - final String SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING = "SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING"; + static final String SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING = "SIMPLIFY_ANYOF_STRING_AND_ENUM_STRING"; // when set to true, oneOf/anyOf schema with only one sub-schema is simplified to just the sub-schema // and if sub-schema contains "null", remove it and set nullable to true instead // and if sub-schema contains enum of "null", remove it and set nullable to true instead - final String SIMPLIFY_ONEOF_ANYOF = "SIMPLIFY_ONEOF_ANYOF"; + static final String SIMPLIFY_ONEOF_ANYOF = "SIMPLIFY_ONEOF_ANYOF"; // when set to true, boolean enum will be converted to just boolean - final String SIMPLIFY_BOOLEAN_ENUM = "SIMPLIFY_BOOLEAN_ENUM"; + static final String SIMPLIFY_BOOLEAN_ENUM = "SIMPLIFY_BOOLEAN_ENUM"; // when set to true, oneOf/anyOf with enum sub-schemas containing single values will be converted to a single enum - final String SIMPLIFY_ONEOF_ANYOF_ENUM = "SIMPLIFY_ONEOF_ANYOF_ENUM"; + static final String SIMPLIFY_ONEOF_ANYOF_ENUM = "SIMPLIFY_ONEOF_ANYOF_ENUM"; // when set to a string value, tags in all operations will be reset to the string value provided - final String SET_TAGS_FOR_ALL_OPERATIONS = "SET_TAGS_FOR_ALL_OPERATIONS"; + static final String SET_TAGS_FOR_ALL_OPERATIONS = "SET_TAGS_FOR_ALL_OPERATIONS"; String setTagsForAllOperations; // when set to true, tags in all operations will be set to operationId or "default" if operationId // is empty - final String SET_TAGS_TO_OPERATIONID = "SET_TAGS_TO_OPERATIONID"; + static final String SET_TAGS_TO_OPERATIONID = "SET_TAGS_TO_OPERATIONID"; String setTagsToOperationId; // when set to a string value, tags will be set to the value of the provided vendor extension @@ -118,40 +119,44 @@ public class OpenAPINormalizer { // when set to true, auto fix integer with maximum value 4294967295 (2^32-1) or long with 18446744073709551615 (2^64-1) // by adding x-unsigned to the schema - final String ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE = "ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE"; + static final String ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE = "ADD_UNSIGNED_TO_INTEGER_WITH_INVALID_MAX_VALUE"; // when set to true, refactor schema with allOf and properties in the same level to a schema with allOf only and // the allOf contains a new schema containing the properties in the top level - final String REFACTOR_ALLOF_WITH_PROPERTIES_ONLY = "REFACTOR_ALLOF_WITH_PROPERTIES_ONLY"; + static final String REFACTOR_ALLOF_WITH_PROPERTIES_ONLY = "REFACTOR_ALLOF_WITH_PROPERTIES_ONLY"; // when set to true, remove the "properties" of a schema with type other than "object" - final String REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT = "REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT"; + static final String REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT = "REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT"; // when set to true, normalize OpenAPI 3.1 spec to make it work with the generator - final String NORMALIZE_31SPEC = "NORMALIZE_31SPEC"; + static final String NORMALIZE_31SPEC = "NORMALIZE_31SPEC"; // when set to true, remove x-internal: true from models, operations - final String REMOVE_X_INTERNAL = "REMOVE_X_INTERNAL"; - boolean removeXInternal; + static final String REMOVE_X_INTERNAL = "REMOVE_X_INTERNAL"; // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else final String FILTER = "FILTER"; + Filter filter = new Filter(); // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else - final String SET_CONTAINER_TO_NULLABLE = "SET_CONTAINER_TO_NULLABLE"; + static final String SET_CONTAINER_TO_NULLABLE = "SET_CONTAINER_TO_NULLABLE"; HashSet setContainerToNullable = new HashSet<>(); boolean updateArrayToNullable; boolean updateSetToNullable; boolean updateMapToNullable; // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else - final String SET_PRIMITIVE_TYPES_TO_NULLABLE = "SET_PRIMITIVE_TYPES_TO_NULLABLE"; + static final String SET_PRIMITIVE_TYPES_TO_NULLABLE = "SET_PRIMITIVE_TYPES_TO_NULLABLE"; HashSet setPrimitiveTypesToNullable = new HashSet<>(); boolean updateStringToNullable; boolean updateIntegerToNullable; boolean updateNumberToNullable; boolean updateBooleanToNullable; + // when set (e.g. internal:unused), remove the x-internalt:true elements and remove the unused elements + static final String REMOVE_FILTER = "REMOVE_FILTER"; + RemoveFilter removeFilter = new RemoveFilter(); + // ============= end of rules ============= /** @@ -209,6 +214,7 @@ public OpenAPINormalizer(OpenAPI openAPI, Map inputRules) { ruleNames.add(SET_CONTAINER_TO_NULLABLE); ruleNames.add(SET_PRIMITIVE_TYPES_TO_NULLABLE); ruleNames.add(SIMPLIFY_ONEOF_ANYOF_ENUM); + ruleNames.add(REMOVE_FILTER); ruleNames.add(REMOVE_PROPERTIES_FROM_TYPE_OTHER_THAN_OBJECT); // rules that are default to true @@ -320,6 +326,38 @@ public void processRules(Map inputRules) { if (bearerAuthSecuritySchemeName != null) { rules.put(SET_BEARER_AUTH_FOR_NAME, true); } + + if (Boolean.TRUE.equals(getRule(FILTER))) { + String filters = inputRules.get(FILTER); + this.filter = createFilter(this.openAPI, filters); + this.filter.parse(); + } + + String removeFilter = inputRules.get(REMOVE_FILTER); + // for backward compatibility + if (getRule(REMOVE_X_INTERNAL)) { + removeFilter = removeFilter == null? REMOVE_X_INTERNAL : removeFilter + " ; " + REMOVE_X_INTERNAL; + } + // for backward compatibility + if (getRule(KEEP_ONLY_FIRST_TAG_IN_OPERATION)) { + removeFilter = removeFilter == null? KEEP_ONLY_FIRST_TAG_IN_OPERATION : removeFilter + " ; " + KEEP_ONLY_FIRST_TAG_IN_OPERATION; + } + if (removeFilter != null) { + this.removeFilter = createRemoveFilter(removeFilter); + this.removeFilter.parse(); + rules.put(REMOVE_FILTER, true); + } + } + + /** + * create the remove filter to process the REMOVE_FILTER normalizer. + * Ovveride this to create a custom remove filter normalizer. + * + * @param filter full filter value, including REMOVE_X_INTERNAL and KEEP_ONLY_FIRST_TAG_IN_OPERATION + * @return a remove filter containing the parsed filters. + */ + protected RemoveFilter createRemoveFilter(String filter) { + return new RemoveFilter(filter); } /** @@ -357,6 +395,7 @@ protected void normalize() { normalizeComponentsSecuritySchemes(); normalizeComponentsSchemas(); normalizeComponentsResponses(); + processRemoveItems(); } /** @@ -372,6 +411,184 @@ protected void normalizeInfo() { } } + /** + * Processes the removal of unused items based on the specified options in the {@code removeUnused} configuration. + * This method performs distinct actions to remove various types of unused elements. + * + * It evaluates each specific removal option. If a particular option is enabled, the associated processing + * sub-method is invoked to perform the cleanup operation. The following operations may be performed: + * + * - Removal of internal operations (via {@code processRemoveInternal()}). + * - Removal of unused schemas (via {@code processRemoveUnusedSchemas()}). + * - Removal of internal schemas (via {@code processRemoveInternalSchemas()}). + * - Removal of unused tags (via {@code processRemoveUnusedTags()}). + * + * Each sub-method is specifically tasked with cleaning its respective category of unused items. + * This modular approach ensures that only operations matching enabled options are executed. + * + * This method is designed to facilitate cleanup operations for unused elements, + * improving overall efficiency and maintainability by reducing unnecessary elements. + */ + + protected void processRemoveItems() { + if (!removeFilter.hasFilter) { + return; + } + processRemoveEmptyPath(); + processRemoveUnusedRequestBodies(); + processRemoveEmptyResponseHeaders(); + processRemoveUnusedResponses(); + processRemoveUnusedParameters(); + processRemoveSchemas(); + if (removeFilter.unusedSchemas) { + processRemoveUnusedSchemas(); + } + processRemoveUnusedTags(); + } + + /** + * remove schemas not used anyware + */ + protected void processRemoveUnusedSchemas() { + List unusedSchemas = ModelUtils.getUnusedSchemas(openAPI); + if (!unusedSchemas.isEmpty()) { + openAPI.getComponents().getSchemas().keySet().removeAll(unusedSchemas); + } + } + + /** + * remove paths with no operations + */ + protected void processRemoveEmptyPath() { + Paths paths = openAPI.getPaths(); + if (paths != null) { + // remove empty pathItems + paths.keySet().removeIf(path -> paths.get(path).readOperations().isEmpty()); + } + } + + protected void processRemoveUnusedRequestBodies() { + if (removeFilter.unusedRequestBodies) { + if (openAPI.getComponents() != null && openAPI.getComponents().getRequestBodies() != null) { + Set usedRequestBodies = openAPI.getPaths().values().stream() + .flatMap(pathItem -> pathItem.readOperations().stream()) + .map(Operation::getRequestBody) + .filter(Objects::nonNull) + .map(RequestBody::get$ref) + .filter(Objects::nonNull) + .map(ModelUtils::getSimpleRef) + .collect(Collectors.toSet()); + + openAPI.getComponents().getRequestBodies().keySet().removeIf(ref -> !usedRequestBodies.contains(ref)); + if (openAPI.getComponents().getRequestBodies().isEmpty()) { + openAPI.getComponents().setRequestBodies(null); + } + } + } + } + + protected void processRemoveEmptyResponseHeaders() { + if (openAPI.getComponents() != null && openAPI.getComponents().getResponses() != null) { + openAPI.getComponents().getResponses() + .forEach((key, value) -> processEmptyResponseHeaders(value)); + } + if (openAPI.getPaths()!= null) { + openAPI.getPaths().values().stream() + .flatMap(pathItem -> pathItem.readOperations().stream()) + .map(Operation::getResponses) + .filter(Objects::nonNull) + .map(ApiResponses::values) + .flatMap(responses -> responses.stream()) + .forEach(this::processEmptyResponseHeaders); + } + } + + protected void processRemoveUnusedResponses() { + if (removeFilter.unusedResponses) { + if (openAPI.getComponents() != null && openAPI.getComponents().getResponses() != null && openAPI.getPaths() != null) { + Set unusedResponses = openAPI.getPaths().values().stream() + .flatMap(pathItem -> pathItem.readOperations().stream()) + .map(Operation::getResponses) + .filter(Objects::nonNull) + .map(ApiResponses::values) + .flatMap(responses-> responses.stream()) + .filter(Objects::nonNull) + .map(ApiResponse::get$ref) + .filter(Objects::nonNull) + .map(ModelUtils::getSimpleRef) + .collect(Collectors.toSet()); + + openAPI.getComponents().getResponses().keySet().removeIf(ref -> !unusedResponses.contains(ref)); + if (openAPI.getComponents().getResponses().isEmpty()) { + openAPI.getComponents().setResponses(null); + } + } + } + } + + protected void processEmptyResponseHeaders(ApiResponse response) { + if (response.getHeaders() != null && response.getHeaders().isEmpty()) { + response.setHeaders(null); + } + } + + protected void processRemoveUnusedParameters() { + if (removeFilter.unusedParameters) { + if (openAPI.getComponents() != null && openAPI.getComponents().getParameters() != null) { + Set usedParameters = openAPI.getPaths().values().stream() + .flatMap(pathItem -> pathItem.readOperations().stream()) + .map(Operation::getParameters) + .filter(Objects::nonNull) + .flatMap(List::stream) + .map(Parameter::get$ref) + .filter(Objects::nonNull) + .map(ModelUtils::getSimpleRef) + .collect(Collectors.toSet()); + + openAPI.getComponents().getParameters().keySet().removeIf(ref -> !usedParameters.contains(ref)); + if (openAPI.getComponents().getParameters().isEmpty()) { + openAPI.getComponents().setParameters(null); + } + } + } + } + + /** + * remove the configured schemas + *

+ * for example: marked as x-internal=true) + */ + protected void processRemoveSchemas() { + Map schemas = Optional.ofNullable(openAPI.getComponents()).map(Components::getSchemas).orElse(null); + if (schemas != null) { + schemas.values().removeIf(schema -> removeFilter.matchSchema(schema)); + } + } + + /** + * Processes the removal of unused tags. + *

+ * This method identifies and cleans up tags that are no longer used or referenced. + */ + protected void processRemoveUnusedTags() { + Paths paths = openAPI.getPaths(); + if (paths != null) { + Set usedTags = paths.values().stream() + .map(PathItem::readOperations) + .flatMap(List::stream) + .map(Operation::getTags) + .filter(Objects::nonNull) + .flatMap(List::stream) + .collect(Collectors.toSet()); + if (openAPI.getTags() != null) { + openAPI.getTags().removeIf(tag -> removeFilter.matchTagToRemove(tag, usedTags)); + if (openAPI.getTags().isEmpty()) { + openAPI.setTags(null); + } + } + } + } + /** * Normalizes inline models in Paths */ @@ -385,25 +602,11 @@ protected void normalizePaths() { PathItem path = pathsEntry.getValue(); List operations = new ArrayList<>(path.readOperations()); - Map> methodMap = Map.of( - "get", PathItem::getGet, - "put", PathItem::getPut, - "head", PathItem::getHead, - "post", PathItem::getPost, - "delete", PathItem::getDelete, - "patch", PathItem::getPatch, - "options", PathItem::getOptions, - "trace", PathItem::getTrace - ); - - if (Boolean.TRUE.equals(getRule(FILTER))) { - String filters = inputRules.get(FILTER); - Filter filter = createFilter(this.openAPI, filters); - if (filter.parse()) { - // Iterates over each HTTP method in methodMap, retrieves the corresponding Operations from the PathItem, - // and marks it as internal (`x-internal=true`) if the method/operationId/tag/path is not in the filters. - filter.apply(pathsEntry.getKey(), path, methodMap); - } + + if (filter.hasFilter()) { + // Iterates over each HTTP method in methodMap, retrieves the corresponding Operations from the PathItem, + // and marks it as internal (`x-internal=true`) if the method/operationId/tag/path is not in the filters. + filter.apply(pathsEntry.getKey(), path, path.readOperationsMap()); } // Include callback operation as well @@ -418,12 +621,34 @@ protected void normalizePaths() { } // normalize PathItem common parameters - normalizeParameters(path.getParameters()); + normalizeParameters(pathsEntry.getKey(), path, null, null, path.getParameters()); + if (removeFilter.hasFilter()) { + // for backward compatibility, keep empty parameters if not removeFilter + if (path.getParameters() != null && path.getParameters().isEmpty()) { + path.setParameters(null); + } + } - for (Operation operation : operations) { + if (removeFilter.hasFilter()) { + Map httpMethodOperationMap = path.readOperationsMap(); + for (Map.Entry operationEntry: httpMethodOperationMap.entrySet()) { + if (removeFilter.matchOperation(pathsEntry.getKey(), operationEntry.getKey(), operationEntry.getValue())) { + path.operation(operationEntry.getKey(), null); + } + } + } + for (Map.Entry operationEntry : path.readOperationsMap().entrySet()) { + Operation operation = operationEntry.getValue(); + PathItem.HttpMethod method = operationEntry.getKey(); normalizeOperation(operation); normalizeRequestBody(operation); - normalizeParameters(operation.getParameters()); + normalizeParameters(pathsEntry.getKey(), path, method, operation, operation.getParameters()); + if (removeFilter.hasFilter()) { + // just for backward compatibility + if (operation.getParameters() != null && operation.getParameters().isEmpty()) { + operation.setParameters(null); + } + } normalizeResponses(operation); } } @@ -436,7 +661,9 @@ protected void normalizePaths() { */ protected void normalizeOperation(Operation operation) { processRemoveXInternalFromOperation(operation); + processRemoveVendorExtensions(operation); + processRemoveTagsInOperation(operation); processKeepOnlyFirstTagInOperation(operation); processSetTagsForAllOperations(operation); @@ -496,6 +723,35 @@ protected void normalizeRequestBody(Operation operation) { normalizeContent(requestBody.getContent()); } + protected void normalizeParameters(String path, PathItem pathItem, PathItem.HttpMethod method, Operation operation, List parameters) { + if (parameters == null) { + return; + } + if (filter.hasFilter()) { + for (Iterator it = parameters.iterator(); it.hasNext(); ) { + Parameter parameter = it.next(); + if (parameter.get$ref() != null) { + parameter = ModelUtils.getReferencedParameter(openAPI, parameter); + } + filter.applyParameter(path, pathItem, method, operation, parameter); + } + } + if (removeFilter.hasFilter()) { + for (Iterator it = parameters.iterator(); it.hasNext(); ) { + Parameter parameter = it.next(); + if (parameter.get$ref() != null) { + parameter = ModelUtils.getReferencedParameter(openAPI, parameter); + } + if (operation == null && removeFilter.matchParameter(path, pathItem, parameter)) { + it.remove(); + } else if (removeFilter.matchParameter(path, pathItem, operation, parameter)) { + it.remove(); + } + } + } + normalizeParameters(parameters); + } + /** * Normalizes schemas in parameters * @@ -557,6 +813,14 @@ protected void normalizeHeaders(Map headers) { return; } + if (removeFilter.hasFilter()) { + Set headersToRemove = headers.entrySet().stream() + .filter(entry -> removeFilter.matchHeader(entry.getKey(), ModelUtils.getReferencedHeader(openAPI, entry.getValue()))) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + headers.keySet().removeIf(key -> headersToRemove.contains(key)); + } + for (String headerKey : headers.keySet()) { Header h = headers.get(headerKey); Schema updatedHeader = normalizeSchema(h.getSchema(), new HashSet<>()); @@ -609,11 +873,13 @@ protected void normalizeComponentsSchemas() { if (schema == null) { LOGGER.warn("{} not found in openapi/components/schemas.", schemaName); } else { - // remove x-internal if needed - if (schema.getExtensions() != null && getRule(REMOVE_X_INTERNAL)) { - if (Boolean.parseBoolean(String.valueOf(schema.getExtensions().get(X_INTERNAL)))) { - schema.getExtensions().remove(X_INTERNAL); - } + if (removeFilter.hasFilter && schema.getExtensions() != null && schema.getExtensions().size() > 0) { + Set extensionKeysToRemove = ((Map)schema.getExtensions()) + .entrySet().stream() + .filter(entry -> removeFilter.matchVendorExtensionToRemove(schema, entry.getKey(), entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + extensionKeysToRemove.forEach(key -> schema.getExtensions().remove(key)); } // auto fix self reference schema to avoid stack overflow @@ -625,6 +891,18 @@ protected void normalizeComponentsSchemas() { } } + public static boolean isInternal(Map extensions) { + if (extensions != null) { + Object xInternalValue = extensions.get(X_INTERNAL); + if (xInternalValue instanceof Boolean) { + return (Boolean) xInternalValue; + } else if (xInternalValue instanceof String) { + return Boolean.parseBoolean((String) xInternalValue); + } + } + return false; + } + /** * Normalizes schemas in component's responses. */ @@ -830,27 +1108,48 @@ protected void normalizeProperties(Map properties, Set v if (properties == null) { return; } + if (this.removeFilter.hasFilter()) { + Set propertiesToRemove = properties.entrySet().stream() + .filter(entry -> this.removeFilter.matchInlineProperties(entry.getValue(), entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + if (!properties.isEmpty()) { + propertiesToRemove.forEach(p -> properties.remove(p) ); + } + } for (Map.Entry propertiesEntry : properties.entrySet()) { Schema property = propertiesEntry.getValue(); - + // remove x-internal if needed (same logic as normalizeComponentsSchemas) - if (property.getExtensions() != null && getRule(REMOVE_X_INTERNAL)) { - Object xInternalValue = property.getExtensions().get(X_INTERNAL); - boolean isInternal = false; - if (xInternalValue instanceof Boolean) { - isInternal = (Boolean) xInternalValue; - } else if (xInternalValue instanceof String) { - isInternal = Boolean.parseBoolean((String) xInternalValue); - } - if (isInternal) { - property.getExtensions().remove(X_INTERNAL); - } + if (property.getExtensions() != null) { + processRemoveVendorExtensions(property, property.getExtensions()); } Schema newProperty = normalizeSchema(property, new HashSet<>()); propertiesEntry.setValue(newProperty); } } + protected void processRemoveVendorExtensions(Schema property, Map extensions) { + if (removeFilter.hasFilter()) { + Set extensionKeysToRemove = extensions + .entrySet().stream() + .filter(entry -> removeFilter.matchVendorExtensionToRemove(property, entry.getKey(), entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + extensions.keySet().removeIf(key -> extensionKeysToRemove.contains(key)); + } + } + + protected void processRemoveVendorExtensions(Operation operation) { + if (operation.getExtensions() != null) { + Set extensionKeysToRemove = operation.getExtensions().entrySet().stream() + .filter(entry -> removeFilter.matchVendorExtensionToRemove(operation, entry.getKey(), entry.getValue())) + .map(Map.Entry::getKey) + .collect(Collectors.toSet()); + operation.getExtensions().keySet().removeIf(key -> extensionKeysToRemove.contains(key)); + } + } + protected void refactorAllOfWithMetadataOnlySchemas(Schema schema) { if (schema.getAllOf() == null) { return; @@ -1097,17 +1396,28 @@ protected void processUseAllOfRefAsParent(Schema schema) { * * @param operation Operation */ + @Deprecated protected void processRemoveXInternalFromOperation(Operation operation) { - if (!getRule(REMOVE_X_INTERNAL)) { - return; - } - - if (operation.getExtensions() == null) { - return; - } + } - if (Boolean.parseBoolean(String.valueOf(operation.getExtensions().get(X_INTERNAL)))) { - operation.getExtensions().remove(X_INTERNAL); + /** + * Remove tag if requested. + * + * @param operation Operation + */ + protected void processRemoveTagsInOperation(Operation operation) { + if (removeFilter.hasFilter && operation.getTags() != null) { + List tags = operation.getTags(); + Set names = new HashSet<>(); + for (int i=0; i < tags.size(); i++) { + if (removeFilter.matchTagToRemove(operation, i, tags.get(i))) { + names.add(tags.get(i)); + } + } + operation.getTags().removeIf(tag -> names.contains(tag)); + if (operation.getTags().isEmpty()) { + operation.setTags(null); + } } } @@ -1117,17 +1427,9 @@ protected void processRemoveXInternalFromOperation(Operation operation) { * * @param operation Operation */ + @Deprecated protected void processKeepOnlyFirstTagInOperation(Operation operation) { - if (!getRule(KEEP_ONLY_FIRST_TAG_IN_OPERATION)) { - return; - } - - if (operation.getTags() != null && !operation.getTags().isEmpty() && operation.getTags().size() > 1) { - // has more than 1 tag - String firstTag = operation.getTags().get(0); - operation.setTags(null); - operation.addTagsItem(firstTag); - } + // kept for backward compatibility } /** @@ -1817,11 +2119,17 @@ protected static class Filter { public static final String PATH = "path"; private final String filters; protected Set operationIdFilters = Collections.emptySet(); - protected Set methodFilters = Collections.emptySet(); + protected Set methodFilters = Collections.emptySet(); protected Set tagFilters = Collections.emptySet(); protected Set pathStartingWithFilters = Collections.emptySet(); + protected Map> vendorExtensions = new HashMap<>(); private boolean hasFilter; + Filter() { + this.filters = null; + this.hasFilter = false; + } + protected Filter(String filters) { this.filters = filters.trim(); } @@ -1861,11 +2169,16 @@ private void doParse() { if (OPERATION_ID.equals(filterKey)) { operationIdFilters = parsedFilters; } else if (METHOD.equals(filterKey)) { - methodFilters = parsedFilters; + methodFilters = parsedFilters.stream() + .map(method -> method.toUpperCase(Locale.ROOT)) + .map(PathItem.HttpMethod::valueOf) + .collect(Collectors.toSet()); } else if (TAG.equals(filterKey)) { tagFilters = parsedFilters; } else if (PATH.equals(filterKey)) { pathStartingWithFilters = parsedFilters; + } else if (filterKey.startsWith("x-")) { + vendorExtensions.put(filterKey, parsedFilters); } else { parse(filterKey, filterValue); } @@ -1873,18 +2186,6 @@ private void doParse() { } } - /** - * Split the filterValue by pipe. - * - * @return the split values. - */ - protected Set splitByPipe(String filterValue) { - return Arrays.stream(filterValue.split("[|]")) - .filter(Objects::nonNull) - .map(String::trim) - .collect(Collectors.toCollection(HashSet::new)); - } - /** * Parse non default filters. * @@ -1903,6 +2204,10 @@ protected void parseFails(String filterName, String filterValue) { throw new IllegalArgumentException("filter not supported :[" + filterName + ":" + filterValue + "]"); } + protected Set splitByPipe(String filterValue) { + return OpenAPINormalizer.splitByPipe(filterValue); + } + /** * Test if the OpenAPI contract match an extra filter. * @@ -1921,31 +2226,95 @@ public boolean hasFilter() { return hasFilter; } - public void apply(String path, PathItem pathItem, Map> methodMap) { - methodMap.forEach((method, getter) -> { - Operation operation = getter.apply(pathItem); - if (operation != null) { - boolean found = false; - found |= logIfMatch(PATH, operation, hasPathStarting(path)); - found |= logIfMatch(TAG, operation, hasTag(operation)); - found |= logIfMatch(OPERATION_ID, operation, hasOperationId(operation)); - found |= logIfMatch(METHOD, operation, hasMethod(method)); - found |= hasCustomFilterMatch(path, operation); + public void apply(String path, PathItem pathItem, Map methodMap) { + methodMap.forEach((method, operation) -> { - operation.addExtension(X_INTERNAL, !found); + String operationInfo = getOperationInfo(path, pathItem, method, operation); + boolean found = false; + found |= logIfMatch(PATH, operationInfo, hasPathStarting(path)); + found |= logIfMatch(TAG, operationInfo, hasTag(operation)); + found |= logIfMatch(OPERATION_ID, operationInfo, hasOperationId(operation)); + found |= logIfMatch(METHOD, operationInfo, hasMethod(method)); + found |= hasCustomFilterMatch(path, operation); + found |= useVendorExtensions(operation) && logIfMatch(this.vendorExtensions, operationInfo, matchVendorExtension(path, method, operation)); + + if (!found) { + logMismatch(operationInfo); } + operation.addExtension(X_INTERNAL, !found); }); } - protected boolean logIfMatch(String filterName, Operation operation, boolean filterMatched) { + private String getOperationInfo(String path, PathItem pathItem, PathItem.HttpMethod method, Operation operation) { + return String.format(Locale.ROOT, "%s %s (operationId: %s)", method, path, operation.getOperationId()); + } + + protected boolean useVendorExtensions(Operation operation) { + return !isEmpty(this.vendorExtensions);// && !isEmpty(operation.getExtensions()); + } + + protected boolean matchVendorExtension(String path, PathItem.HttpMethod method, Operation operation) { + return matchExtension(operation.getExtensions()); + } + + public void applyParameter(String path, PathItem pathItem, PathItem.HttpMethod method, Operation operation, Parameter parameter) { + Map parameterExtensions = parameter.getExtensions(); + if (this.vendorExtensions == null || isEmpty(parameterExtensions)) { + return; + } + if (operation != null) { + if (!matchParameterExtension(path, method, operation, parameter)) { + parameter.addExtension(X_INTERNAL, true); + String operationInfo = getOperationInfo(path, pathItem, method, operation); + getLogger().info("Parameter `{}` in `{}` marked as internal (x-internal: true) by FILTER", parameter.getName(), operationInfo); + } + } else { + if (!matchParameterExtension(path, parameter)) { + parameter.addExtension(X_INTERNAL, true); + getLogger().info("Parameter `{}` in `{}` marked as internal (x-internal: true) by FILTER", parameter.getName(), path); + } + } + } + + protected boolean matchParameterExtension(String path, PathItem.HttpMethod method, Operation operation, Parameter parameter) { + return matchExtension(parameter.getExtensions()); + } + + protected boolean matchParameterExtension(String path, Parameter parameter) { + return matchExtension(parameter.getExtensions()); + } + + + protected boolean logIfMatch(Object filterName, String operation, boolean filterMatched) { if (filterMatched) { - logMatch(filterName, operation); + logIfMatch(filterName, operation); } return filterMatched; } - protected void logMatch(String filterName, Operation operation) { - getLogger().info("operation `{}` marked as internal only (x-internal: true) by the {} FILTER", operation.getOperationId(), filterName); + protected void logMismatch(String operation) { + getLogger().debug("operation `{}` marked as internal only (x-internal: true) by FILTER", operation); + } + + + protected void logIfMatch(Object filterName, String operation) { + getLogger().info("operation `{}` kept (x-internal: false) by the {} FILTER", operation, filterName); + } + + protected boolean matchExtension(Map extensions) { + if ( isEmpty(extensions)) { + return true; + } + for (Map.Entry> entry : this.vendorExtensions.entrySet()) { + String key = entry.getKey(); + if (extensions.containsKey(key)) { + Set filter = entry.getValue(); + if (!filter.contains(extensions.get(key))) { + return false; + } + } + } + return true; } protected Logger getLogger() { @@ -1964,9 +2333,10 @@ private boolean hasOperationId(Operation operation) { return operationIdFilters.contains(operation.getOperationId()); } - private boolean hasMethod(String method) { + private boolean hasMethod(PathItem.HttpMethod method) { return methodFilters.contains(method); } + } /** @@ -1986,4 +2356,411 @@ protected void processNormalizeOtherThanObjectWithProperties(Schema schema) { } } } + + public static class RemoveFilter { + private final static Set ANYSET = Collections.singleton("any"); + public static final String REMOVE_TAGS = "removeTags"; + public static final String REMOVE_VENDOR_EXTENSIONS = "removeVendorExtensions"; + public static final String DEPRECATED = "deprecated"; + public static final String UNUSED = "unused"; + public static final String TAGS = "tags"; + public static final String INTERNAL = "internal"; + public static final String HEADERS = "headers"; + private static final String QUERYPARAMS = "queryParams"; + private static final String COOKIES = "cookies"; + private final String filters; + private boolean hasFilter; + + protected boolean removeXInternal; + protected Set removeExtensions = Collections.emptySet(); + + protected boolean internalOperations; + protected boolean internalSchemas; + protected boolean internalParameters; + protected boolean internalProperties; + + protected boolean unusedSchemas; + protected boolean unusedParameters; + protected boolean unusedTags; + protected boolean unusedRequestBodies; + protected boolean unusedResponses; + + protected boolean keepOnlyFirstTagInOperation; + protected Set removeTags = Collections.emptySet(); + protected Set headers = Collections.emptySet(); + protected Set cookies = Collections.emptySet(); + protected Set queryParams = Collections.emptySet(); + protected boolean deprecated; + protected Set tags = Collections.emptySet(); + protected Map> vendorExtensions = new HashMap<>(); + + RemoveFilter() { + this.filters = null; + } + + protected RemoveFilter(String filters) { + this.filters = filters.trim(); + } + + /** + * Perform the parsing of the filter string. + * + * @return true if filters need to be processed + */ + public boolean parse() { + if (StringUtils.isEmpty(filters)) { + return false; + } + try { + doParse(); + return hasFilter(); + } catch (RuntimeException e) { + String message = String.format(Locale.ROOT, "REMOVE_FILTER rule [%s] `. Error: %s", + filters, e.getMessage()); + throw new IllegalArgumentException(message); + } + } + + public boolean hasFilter() { + return hasFilter; + } + + private void doParse() { + for (String filter : filters.split(";")) { + filter = filter.trim(); + String[] filterStrs = filter.split(":"); + boolean isTrue = false; + boolean isBoolean = false; + boolean isAny = filterStrs.length == 1; + Set set; + String filterKey = filterStrs[0].trim(); + String filterValue; + if (isAny) { + isTrue = true; + isBoolean = true; + set = Collections.emptySet(); + filterValue = null; + } else { + filterValue = filterStrs[1]; + set = splitByPipe(filterValue); + isTrue = Boolean.parseBoolean(filterValue); + isBoolean = "false".equals(filterValue) || isTrue; + } + + Set anyOrSet = isAny? getAnySet(isTrue): set; + hasFilter = true; + + if (INTERNAL.equals(filterKey)) { + if (isBoolean) { + internalOperations = isTrue; + internalSchemas = isTrue; + internalProperties = isTrue; + internalParameters = isTrue; + } else { + internalOperations = set.contains("operations"); + internalSchemas = set.contains("schemas"); + internalProperties = set.contains("properties"); + internalParameters = set.contains("parameters"); + } + } else if (DEPRECATED.equals(filterKey)) { + this.deprecated = isTrue; + } else if (UNUSED.equals(filterKey)) { + if (isBoolean) { + unusedSchemas = isTrue; + unusedTags = isTrue; + unusedRequestBodies = isTrue; + unusedResponses = isTrue; + unusedParameters = isTrue; + } else { + unusedSchemas = set.contains("schemas"); + unusedTags = set.contains(TAGS); + unusedRequestBodies = set.contains("requestBodies"); + unusedResponses = set.contains("responses"); + unusedParameters = set.contains("parameters"); + } + } else if (REMOVE_X_INTERNAL.equals(filterKey)) { + removeXInternal = isTrue; + } else if (REMOVE_VENDOR_EXTENSIONS.equals(filterKey)) { + if (isAny) { + removeXInternal = isTrue; + } + removeExtensions = anyOrSet; + } else if (KEEP_ONLY_FIRST_TAG_IN_OPERATION.equals(filterKey)) { + keepOnlyFirstTagInOperation = isTrue; + } else if (REMOVE_TAGS.equals(filterKey)) { + if ("keepOnlyFirstTag".equals(filterValue)) { + keepOnlyFirstTagInOperation = true; + } else { + removeTags = anyOrSet; + } + } else if (TAGS.equals(filterKey)) { + tags = set; + } else if (HEADERS.equals(filterKey)) { + headers = anyOrSet; + } else if (QUERYPARAMS.equals(filterKey)) { + queryParams = anyOrSet; + } else if (COOKIES.equals(filterKey)) { + cookies = anyOrSet; + } else if (filterKey.startsWith("x-")) { + vendorExtensions.put(filterKey, isAny ? getAnySet(isTrue) : set); + } else { + parse(filterKey, filterValue); + } + } + } + + /** + * Parse unknown filter. + *

> + * Override this method to add custom parsing logic. + * Default is to fail. + * + * @param filterName name of the filter + * @param filterValue value of the filter + */ + protected void parse(String filterName, String filterValue) { + parseFails(filterName, filterValue); + } + + /** + * Fails the parsing with an IllegalArgumentException. + */ + protected void parseFails(String filterName, String filterValue) { + throw new IllegalArgumentException("filter not supported :[" + filterName + ":" + filterValue + "]"); + } + + /** + * Evaluate if a tag match the filters. + * + * @param operation owner of the tag + * @param index 0 indexed index in the tag list + * @param tagvalue vendorExtension value + * + * @return true if the vendorExtension needs to be removed + */ + protected boolean matchTagToRemove(Operation operation, int index, String tagvalue) { + if (removeTags == ANYSET) { + return true; + } + if (keepOnlyFirstTagInOperation) { + return index > 0; + } + return removeTags.contains(tagvalue); + } + + /** + * Evaluate if a vendorExtension match the filters. + * + * @param schema owner of the vendorExtension + * @param key vendorExtension key + * @param value vendorExtension value + * + * @return true if the vendorExtension needs to be removed + */ + protected boolean matchVendorExtensionToRemove(Schema schema, String key, Object value) { + return matchVendorExtensionToRemove(key); + } + + /** + * Evaluate if a vendorExtension match the filters. + * + * @param operation owner of the vendorExtension + * @param key vendorExtension key + * @param value vendorExtension value + * + * @return true if the vendorExtension needs to be removed + */ + protected boolean matchVendorExtensionToRemove(Operation operation, String key, Object value) { + return matchVendorExtensionToRemove(key); + } + + /** + * Evaluate if an operation matchs the filters. + * + * @param path endpoint + * @param method parent of the property (get, post, put, etc) + * @param operation operation to evaluate + * @return true if the operation needs to be removed + */ + protected boolean matchOperation(String path, PathItem.HttpMethod method, Operation operation) { + if (internalOperations && isInternal(operation.getExtensions())) { + return true; + } + if (deprecated && Boolean.TRUE.equals(operation.getDeprecated())) { + return true; + } + + return matchExtension(operation.getExtensions()) || (operation.getTags() != null && matchTag(operation.getTags())); + } + + /** + * Evaluate if the operation with tags need to be removed. + * + * @param operationTags tags of the operation + * @return true if the operation needs to be removed + */ + private boolean matchTag(List operationTags) { + if (this.tags.isEmpty()) { + return false; + } + return this.tags.stream().anyMatch(operationTags::contains); + } + + /** + * Evaluate if a property matchs the filters. + * + * @param schema parent of the property + * @param property + * @return true if the property needs to be removed + */ + protected boolean matchInlineProperties(Schema schema, Schema property) { + if (internalProperties && isInternal(property.getExtensions())) { + return true; + } + if (deprecated && Boolean.TRUE.equals(property.getDeprecated())) { + return true; + } + return matchExtension(property.getExtensions()); + } + + /** + * Evaluate if a schema matchs the filters. + * + * @param schema + * @return true if the schema needs to be removed + */ + protected boolean matchSchema(Schema schema) { + if (internalSchemas && isInternal(schema.getExtensions())) { + return true; + } + if (deprecated && Boolean.TRUE.equals(schema.getDeprecated())) { + return true; + } + return matchExtension(schema.getExtensions()); + } + + /** + * Evaluate if the tag matchs the filters. + * + * @param tag + * @param usedTags the names of the tags used by the document + * @return true if the parameter needs to be removed + */ + protected boolean matchTagToRemove(Tag tag, Set usedTags) { + return unusedTags && !usedTags.contains(tag.getName()); + } + + /** + * Evaluate if the parameter matchs the filters. + * + * @param path path of the endpoint + * @param pathItem path item of the endpoint + * @param parameter parameter of the endpoint + * @return true if the parameter needs to be removed + */ + protected boolean matchParameter(String path, PathItem pathItem, Parameter parameter) { + return matchParameter(parameter); + } + + /** + * Evaluate if the parameter matchs the filters. + * + * @param path path of the endpoint + * @param pathItem path item of the endpoint + * @param operation operation of the endpoint + * @param parameter parameter of the endpoint + * @return true if the parameter needs to be removed + */ + protected boolean matchParameter(String path, PathItem pathItem, Operation operation, Parameter parameter) { + return matchParameter(parameter); + } + + protected boolean matchVendorExtensionToRemove(String key) { + if (removeExtensions == ANYSET) { + return true; + } + // remove x-internal if needed + if (removeXInternal && X_INTERNAL.equals(key)) { + return true; + } + + return removeExtensions.contains(key); + } + + /** + * Evaluate if the parameter matchs the filters. + * + * @param parameter + * @return true if the parameter needs to be removed + */ + protected boolean matchParameter(Parameter parameter) { + if (internalParameters && isInternal(parameter.getExtensions())) { + return true; + } + if (deprecated && Boolean.TRUE.equals(parameter.getDeprecated())) { + return true; + } + String in = parameter.getIn(); + if ("query".equals(in) && matchParameter(parameter, this.queryParams)) { + return true; + } + if ("cookie".equals(in) && matchParameter(parameter, this.cookies)) { + return true; + } + if ("header".equals(in) && matchParameter(parameter, this.headers)) { + return true; + } + + return matchExtension(parameter.getExtensions()); + } + + private boolean matchParameter(Parameter parameter, Set params) { + return matchValue(parameter.getName(), params); + } + + private boolean matchValue(Object value, Set valuesToMatch) { + if (isEmpty(valuesToMatch)) { + return false; + } + if (valuesToMatch == ANYSET) { + return true; + } + return valuesToMatch.contains(value); + + } + + protected boolean matchHeader(String key, Header header) { + return matchValue(key, this.headers) || matchExtension(header.getExtensions()); + } + + private boolean matchExtension(Map extensions) { + if (extensions == null || extensions.isEmpty() || vendorExtensions.isEmpty()) { + return false; + } + return extensions.entrySet() + .stream() + .anyMatch(this::matchExtension); + } + + private boolean matchExtension(Map.Entry entry) { + Set set = vendorExtensions.get(entry.getKey()); + return matchValue(entry.getValue(), set); + } + + private Set getAnySet(boolean isTrue) { + return isTrue ? ANYSET : Collections.emptySet(); + } + } + + /** + * Split the filterValue by pipe. + * + * @return the split values. + */ + public static Set splitByPipe(String filterValue) { + return Arrays.stream(filterValue.split("[|]")) + .filter(Objects::nonNull) + .map(String::trim) + .collect(Collectors.toCollection(HashSet::new)); + } } diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java index 118001f798c4..bae509b6b287 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java @@ -16,6 +16,8 @@ package org.openapitools.codegen; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.PathItem; @@ -23,10 +25,16 @@ import io.swagger.v3.oas.models.parameters.Parameter; import io.swagger.v3.oas.models.responses.ApiResponse; import io.swagger.v3.oas.models.security.SecurityScheme; +import lombok.Data; +import org.openapitools.codegen.serializer.SerializerUtils; import org.openapitools.codegen.utils.ModelUtils; +import org.opentest4j.AssertionFailedError; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import java.lang.reflect.Array; +import java.io.*; +import java.net.URL; import java.util.*; import static org.testng.Assert.*; @@ -694,7 +702,7 @@ public void testFilterParsing() { // extra spaces are trimmed filter = parseFilter("method:\n\t\t\t\tget"); assertTrue(filter.hasFilter()); - assertEquals(filter.methodFilters, Set.of("get")); + assertEquals(filter.methodFilters, Set.of(PathItem.HttpMethod.GET)); assertTrue(filter.operationIdFilters.isEmpty()); assertTrue(filter.tagFilters.isEmpty()); assertTrue(filter.pathStartingWithFilters.isEmpty()); @@ -1288,4 +1296,71 @@ public Schema normalizeSchema(Schema schema, Set visitedSchemas) { } } + /** + * get a list of all files matching openapi_normalizer/*_config.yaml. + */ + @DataProvider(name="testConfigs") + public Object[][] getTestConfigs() { + URL path = getClass().getClassLoader().getResource("openapi_normalizer"); + File[] files = new File(path.getFile()) + .listFiles(file -> file.getName().endsWith("_config.yaml")); + + if (files == null || files.length == 0) { + throw new AssertionError("No test configs found in openapi_normalizer directory."); + } + return Arrays.stream(files) + .map(file -> new Object[]{"src/test/resources/openapi_normalizer/" +file.getName()}) + .toArray(Object[][]::new); + } + + @Test(dataProvider = "testConfigs") + public void executeAllTests(String specPath) { + OpenapiNormalizerTestConfig config = OpenapiNormalizerTestConfig.fromFile(specPath); + OpenAPI openAPI = TestUtils.parseSpec(config.inputSpec); + OpenAPINormalizer openAPINormalizer = OpenAPINormalizer.createNormalizer(openAPI, config.inputRules); + openAPINormalizer.normalize(); + String expected = SerializerUtils.toYamlString(TestUtils.parseSpec(config.normalizedSpec)); + String after = SerializerUtils.toYamlString(openAPI); + if (!expected.equals(after)) { + throw new AssertionFailedError("Unexpected normalized result for\n" + config, expected, after); + } + } + + /** + * Custom remove filter used by custom_remove_filter_config.yaml + */ + public static class CustomRemoveFilter extends OpenAPINormalizer { + public CustomRemoveFilter(OpenAPI openAPI, Map inputRules) { + super(openAPI, inputRules); + } + + @Override + protected RemoveFilter createRemoveFilter(String filter) { + return new RemoveFilter(filter) { + protected boolean matchOperation(String path, PathItem.HttpMethod method, Operation operation) { + return operation.getExtensions() != null && "admin".equals(operation.getExtensions().get("x-role")); + } + }; + } + } + + /** + * Normalizer configuration in yaml files. + */ + @Data + static class OpenapiNormalizerTestConfig { + Map inputRules; + String inputSpec; + String normalizedSpec; + + static OpenapiNormalizerTestConfig fromFile(String specPath) { + ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); + try (InputStream inputStream = new FileInputStream(new File(specPath))) { + return objectMapper.readValue(inputStream, OpenapiNormalizerTestConfig.class); + } catch (IOException e) { + throw new RuntimeException("Unable to read from " + specPath, e); + } + } + } + } diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/custom_remove_filter_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/custom_remove_filter_config.yaml new file mode 100644 index 000000000000..ba9781e6d9d1 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/custom_remove_filter_config.yaml @@ -0,0 +1,6 @@ +# test custom remove filter +inputSpec: src/test/resources/openapi_normalizer/petstore_with_roles.yaml +normalizedSpec: src/test/resources/openapi_normalizer/custom_remove_filter_normalized.yaml +inputRules: + NORMALIZER_CLASS: org.openapitools.codegen.OpenAPINormalizerTest$CustomRemoveFilter + REMOVE_FILTER: 'unused' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/custom_remove_filter_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/custom_remove_filter_normalized.yaml new file mode 100644 index 000000000000..06d2987f547e --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/custom_remove_filter_normalized.yaml @@ -0,0 +1,80 @@ +openapi: 3.0.0 +info: + version: 1.0.0 +servers: + - url: http://petstore.swagger.io/v2 +paths: + /pet/{petId}: + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + put: + requestBody: + $ref: "#/components/requestBodies/Pet" + x-role: superuser +components: + requestBodies: + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + schemas: + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet category + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + type: string + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + x-role: admin + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test.yaml new file mode 100644 index 000000000000..d5a07461f1d5 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test.yaml @@ -0,0 +1,24 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Test inline x-internal +components: + schemas: + ParentSchema: + description: Schema with inline x-internal property + type: object + properties: + normalProperty: + type: string + description: A normal property without x-internal + inlineXInternalProperty: + x-internal: true + description: Inline object property marked as x-internal + type: object + properties: + nestedField: + type: string + description: A field inside the inline x-internal object + nestedNumber: + type: integer + description: Another field inside the inline object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test_config.yaml new file mode 100644 index 000000000000..e687b1188936 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test_config.yaml @@ -0,0 +1,5 @@ +# remove the elements having x-internal: true +inputSpec: src/test/resources/openapi_normalizer/inline_x_internal_test.yaml +normalizedSpec: src/test/resources/openapi_normalizer/inline_x_internal_test_normalized.yaml +inputRules: + REMOVE_FILTER: 'internal: properties' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test_normalized.yaml new file mode 100644 index 000000000000..f5365ec954c6 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/inline_x_internal_test_normalized.yaml @@ -0,0 +1,14 @@ +openapi: 3.0.0 +info: + version: 1.0.0 + title: Test inline x-internal +components: + schemas: + ParentSchema: + description: Schema with inline x-internal property + type: object + properties: + normalProperty: + type: string + description: A normal property without x-internal + diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore.yaml new file mode 100644 index 000000000000..dc8f876992be --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore.yaml @@ -0,0 +1,533 @@ +openapi: 3.0.0 +info: + description: >- + This is a sample server Petstore server. + version: 1.0.0 +servers: + - url: http://petstore.swagger.io/v2 +tags: + - name: pet + description: Everything about your Pets + - name: store + description: Access to Petstore orders + - name: user + description: Operations about user +paths: + /pet: + post: + tags: + - pet + operationId: addPet + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + '405': + description: Invalid input + requestBody: + $ref: '#/components/requestBodies/Pet' + put: + tags: + - pet + operationId: updatePet + externalDocs: + url: "http://petstore.swagger.io/v2/doc/updatePet" + description: "API documentation for the updatePet operation" + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + requestBody: + $ref: '#/components/requestBodies/Pet' + /pet/findByStatus: + parameters: + - in: header + name: role + required: false + deprecated: true + x-internal: true + schema: + type: string + get: + tags: + - pet + operationId: findPetsByStatus + deprecated: true + parameters: + - in: query + name: limit + required: false + deprecated: true + x-internal: true + schema: + type: integer + - $ref: '#/components/parameters/offset' + - name: status + x-role: admin + in: query + description: Status values that need to be considered for filter + required: true + style: form + explode: false + deprecated: true + schema: + type: array + items: + type: string + enum: + - available + - pending + - sold + default: available + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid status value + /pet/findByTags: + get: + tags: + - pet + operationId: findPetsByTags + x-role: guest + parameters: + - name: tags + in: query + description: Tags to filter by + required: true + style: form + explode: false + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid tag value + deprecated: true + '/pet/{petId}': + get: + tags: + - pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + '400': + description: Invalid ID supplied + '404': + description: Pet not found + post: + tags: + - pet + operationId: updatePetWithForm + x-role: admin + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + responses: + '405': + description: Invalid input + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + x-role: admin + parameters: + - name: api_key + in: header + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + /store/inventory: + get: + deprecated: true + tags: + - store + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + /store/order: + post: + tags: + - store + operationId: placeOrder + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + '400': + description: Invalid Order + requestBody: + $ref: '#/components/requestBodies/Order' + '/store/order/{orderId}': + get: + tags: + - store + operationId: getOrderById + parameters: + - $ref: '#/components/parameters/orderId' + responses: + '200': + $ref: '#/components/responses/Order' + + delete: + tags: + - store + operationId: deleteOrder + parameters: + - $ref: '#/components/parameters/orderId' + /user: + post: + tags: + - user + operationId: createUser + responses: + default: + description: successful operation + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Created user object + required: true + /user/createWithList: + post: + tags: + - user + operationId: createUsersWithListInput + requestBody: + $ref: '#/components/requestBodies/UserArray' + /user/login: + get: + tags: + - user + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: true + schema: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + - name: password + in: query + description: The password for login in clear text + required: true + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + x-internal: true + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + content: + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + operationId: logoutUser + '/user/{username}': + get: + tags: + - user + operationId: getUserByName + parameters: + - name: username + in: path + description: The name that needs to be fetched. Use user1 for testing. + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + put: + tags: + - user + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid user supplied + '404': + description: User not found + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/User' + description: Updated user object + required: true + delete: + tags: + - user + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string +externalDocs: + description: Find out more about Swagger + url: 'http://swagger.io' +components: + responses: + Order: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + Order: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + schemas: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + quantity: + type: integer + format: int32 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + enum: + - placed + - approved + - delivered + complete: + type: boolean + default: false + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + pattern: '^[a-zA-Z0-9]+[a-zA-Z0-9\.\-_]*[a-zA-Z0-9]+$' + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + type: integer + format: int32 + description: User Status + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + example: doggie + photoUrls: + type: array + items: + type: string + tags: + type: array + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + deprecated: true + enum: + - available + - pending + - sold + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + parameters: + orderId: + name: orderId + in: path + required: true + schema: + type: string + offset: + in: query + name: offset + required: false + x-internal: true + deprecated: true + schema: + type: integer diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_filter_role_guest.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_filter_role_guest.yaml new file mode 100644 index 000000000000..5caa5309ddaa --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_filter_role_guest.yaml @@ -0,0 +1,578 @@ +openapi: 3.0.0 +info: + description: This is a sample server Petstore server. + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: http://petstore.swagger.io/v2 +tags: + - description: Everything about your Pets + name: pet + - description: Access to Petstore orders + name: store + - description: Operations about user + name: user +paths: + /pet: + post: + operationId: addPet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "405": + description: Invalid input + tags: + - pet + x-internal: false + put: + externalDocs: + description: API documentation for the updatePet operation + url: http://petstore.swagger.io/v2/doc/updatePet + operationId: updatePet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + tags: + - pet + x-internal: false + /pet/findByStatus: + get: + deprecated: true + operationId: findPetsByStatus + parameters: + - deprecated: true + explode: true + in: query + name: limit + required: false + schema: + type: integer + style: form + x-internal: true + - $ref: "#/components/parameters/offset" + - deprecated: true + description: Status values that need to be considered for filter + explode: false + in: query + name: status + required: true + schema: + items: + default: available + enum: + - available + - pending + - sold + type: string + type: array + style: form + x-role: admin + x-internal: true + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid status value + tags: + - pet + x-internal: false + parameters: + - deprecated: true + explode: false + in: header + name: role + required: false + schema: + type: string + style: simple + x-internal: true + /pet/findByTags: + get: + deprecated: true + operationId: findPetsByTags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + style: form + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid tag value + tags: + - pet + x-role: guest + x-internal: false + /pet/{petId}: + delete: + description: "" + operationId: deletePet + parameters: + - explode: false + in: header + name: api_key + required: false + schema: + type: string + style: simple + - description: Pet id to delete + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "400": + description: Invalid pet value + summary: Deletes a pet + tags: + - pet + x-role: admin + x-internal: true + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "400": + description: Invalid ID supplied + "404": + description: Pet not found + tags: + - pet + x-internal: false + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + responses: + "405": + description: Invalid input + tags: + - pet + x-role: admin + x-internal: true + /store/inventory: + get: + deprecated: true + operationId: getInventory + responses: + "200": + content: + application/json: + schema: + additionalProperties: + format: int32 + type: integer + type: object + description: successful operation + tags: + - store + x-internal: false + /store/order: + post: + operationId: placeOrder + requestBody: + $ref: "#/components/requestBodies/Order" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + "400": + description: Invalid Order + tags: + - store + x-internal: false + /store/order/{orderId}: + delete: + operationId: deleteOrder + parameters: + - $ref: "#/components/parameters/orderId" + tags: + - store + x-internal: false + get: + operationId: getOrderById + parameters: + - $ref: "#/components/parameters/orderId" + responses: + "200": + $ref: "#/components/responses/Order" + tags: + - store + x-internal: false + /user: + post: + operationId: createUser + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Created user object + required: true + responses: + default: + description: successful operation + tags: + - user + x-internal: false + /user/createWithList: + post: + operationId: createUsersWithListInput + requestBody: + $ref: "#/components/requestBodies/UserArray" + tags: + - user + x-internal: false + /user/login: + get: + operationId: loginUser + parameters: + - description: The user name for login + explode: true + in: query + name: username + required: true + schema: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + style: form + - description: The password for login in clear text + explode: true + in: query + name: password + required: true + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + type: string + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + explode: false + schema: + format: int32 + type: integer + style: simple + x-internal: true + "400": + description: Invalid username/password supplied + tags: + - user + x-internal: false + /user/logout: + get: + operationId: logoutUser + tags: + - user + x-internal: false + /user/{username}: + delete: + operationId: deleteUser + parameters: + - description: The name that needs to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + tags: + - user + x-internal: false + get: + operationId: getUserByName + parameters: + - description: The name that needs to be fetched. Use user1 for testing. + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: successful operation + tags: + - user + x-internal: false + put: + operationId: updateUser + parameters: + - description: name that need to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Updated user object + required: true + responses: + "400": + description: Invalid user supplied + "404": + description: User not found + tags: + - user + x-internal: false +components: + parameters: + orderId: + explode: false + in: path + name: orderId + required: true + schema: + type: string + style: simple + offset: + deprecated: true + explode: true + in: query + name: offset + required: false + schema: + type: integer + style: form + x-internal: true + requestBodies: + UserArray: + content: + application/json: + schema: + items: + $ref: "#/components/schemas/User" + type: array + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: order placed for purchasing the pet + required: true + responses: + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + schemas: + Order: + description: An order for a pets from the pet store + properties: + id: + format: int64 + type: integer + petId: + format: int64 + type: integer + quantity: + format: int32 + type: integer + shipDate: + format: date-time + type: string + status: + description: Order Status + enum: + - placed + - approved + - delivered + type: string + complete: + default: false + type: boolean + title: Pet Order + type: object + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + title: Pet category + type: object + User: + description: A User who is purchasing from the pet store + properties: + id: + format: int64 + type: integer + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + description: User Status + format: int32 + type: integer + title: a User + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + status: + deprecated: true + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + title: a Pet + type: object + ApiResponse: + description: Describes the result of uploading an image resource + properties: + code: + format: int32 + type: integer + type: + type: string + message: + type: string + title: An uploaded response + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_filter_role_guest_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_filter_role_guest_config.yaml new file mode 100644 index 000000000000..53cf7518c78d --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_filter_role_guest_config.yaml @@ -0,0 +1,5 @@ +# remove the elements having deprecated: true +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_filter_role_guest.yaml +inputRules: + FILTER: 'x-role: guest' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeDeprecated_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeDeprecated_config.yaml new file mode 100644 index 000000000000..389eb0bd5848 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeDeprecated_config.yaml @@ -0,0 +1,5 @@ +# remove the elements having deprecated: true +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_removeDeprecated_normalized.yaml +inputRules: + REMOVE_FILTER: 'deprecated ; unused' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeDeprecated_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeDeprecated_normalized.yaml new file mode 100644 index 000000000000..d9fdb32074a4 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeDeprecated_normalized.yaml @@ -0,0 +1,429 @@ +openapi: 3.0.0 +info: + description: This is a sample server Petstore server. + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: http://petstore.swagger.io/v2 +tags: + - description: Everything about your Pets + name: pet + - description: Access to Petstore orders + name: store + - description: Operations about user + name: user +paths: + /pet: + post: + operationId: addPet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "405": + description: Invalid input + tags: + - pet + put: + externalDocs: + description: API documentation for the updatePet operation + url: http://petstore.swagger.io/v2/doc/updatePet + operationId: updatePet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + tags: + - pet + /pet/{petId}: + delete: + description: "" + operationId: deletePet + parameters: + - explode: false + in: header + name: api_key + required: false + schema: + type: string + style: simple + - description: Pet id to delete + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "400": + description: Invalid pet value + summary: Deletes a pet + tags: + - pet + x-role: admin + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "400": + description: Invalid ID supplied + "404": + description: Pet not found + tags: + - pet + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + responses: + "405": + description: Invalid input + tags: + - pet + x-role: admin + /store/order: + post: + operationId: placeOrder + requestBody: + $ref: "#/components/requestBodies/Order" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + "400": + description: Invalid Order + tags: + - store + /store/order/{orderId}: + delete: + operationId: deleteOrder + parameters: + - $ref: "#/components/parameters/orderId" + tags: + - store + get: + operationId: getOrderById + parameters: + - $ref: "#/components/parameters/orderId" + responses: + "200": + $ref: "#/components/responses/Order" + tags: + - store + /user: + post: + operationId: createUser + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Created user object + required: true + responses: + default: + description: successful operation + tags: + - user + /user/createWithList: + post: + operationId: createUsersWithListInput + requestBody: + $ref: "#/components/requestBodies/UserArray" + tags: + - user + /user/login: + get: + operationId: loginUser + parameters: + - description: The user name for login + explode: true + in: query + name: username + required: true + schema: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + style: form + - description: The password for login in clear text + explode: true + in: query + name: password + required: true + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + type: string + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + explode: false + schema: + format: int32 + type: integer + style: simple + x-internal: true + "400": + description: Invalid username/password supplied + tags: + - user + /user/logout: + get: + operationId: logoutUser + tags: + - user + /user/{username}: + delete: + operationId: deleteUser + parameters: + - description: The name that needs to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + tags: + - user + get: + operationId: getUserByName + parameters: + - description: The name that needs to be fetched. Use user1 for testing. + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: successful operation + tags: + - user + put: + operationId: updateUser + parameters: + - description: name that need to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Updated user object + required: true + responses: + "400": + description: Invalid user supplied + "404": + description: User not found + tags: + - user +components: + parameters: + orderId: + explode: false + in: path + name: orderId + required: true + schema: + type: string + style: simple + requestBodies: + UserArray: + content: + application/json: + schema: + items: + $ref: "#/components/schemas/User" + type: array + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: order placed for purchasing the pet + required: true + responses: + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + schemas: + Order: + description: An order for a pets from the pet store + properties: + id: + format: int64 + type: integer + petId: + format: int64 + type: integer + quantity: + format: int32 + type: integer + shipDate: + format: date-time + type: string + status: + description: Order Status + enum: + - placed + - approved + - delivered + type: string + complete: + default: false + type: boolean + title: Pet Order + type: object + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + title: Pet category + type: object + User: + description: A User who is purchasing from the pet store + properties: + id: + format: int64 + type: integer + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + description: User Status + format: int32 + type: integer + title: a User + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeQueryParams.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeQueryParams.yaml new file mode 100644 index 000000000000..a84918b2abe5 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeQueryParams.yaml @@ -0,0 +1,480 @@ +openapi: 3.0.0 +info: + description: This is a sample server Petstore server. + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: http://petstore.swagger.io/v2 +tags: + - description: Everything about your Pets + name: pet + - description: Access to Petstore orders + name: store + - description: Operations about user + name: user +paths: + /pet: + post: + operationId: addPet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "405": + description: Invalid input + tags: + - pet + put: + externalDocs: + description: API documentation for the updatePet operation + url: http://petstore.swagger.io/v2/doc/updatePet + operationId: updatePet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + tags: + - pet + /pet/findByStatus: + get: + deprecated: true + operationId: findPetsByStatus + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid status value + tags: + - pet + /pet/findByTags: + get: + deprecated: true + operationId: findPetsByTags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + style: form + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid tag value + tags: + - pet + /pet/{petId}: + delete: + description: "" + operationId: deletePet + parameters: + - description: Pet id to delete + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "400": + description: Invalid pet value + summary: Deletes a pet + tags: + - pet + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "400": + description: Invalid ID supplied + "404": + description: Pet not found + tags: + - pet + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + responses: + "405": + description: Invalid input + tags: + - pet + /store/inventory: + get: + deprecated: true + operationId: getInventory + responses: + "200": + content: + application/json: + schema: + additionalProperties: + format: int32 + type: integer + type: object + description: successful operation + tags: + - store + /store/order: + post: + operationId: placeOrder + requestBody: + $ref: "#/components/requestBodies/Order" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + "400": + description: Invalid Order + tags: + - store + /store/order/{orderId}: + delete: + operationId: deleteOrder + parameters: + - $ref: "#/components/parameters/orderId" + tags: + - store + get: + operationId: getOrderById + parameters: + - $ref: "#/components/parameters/orderId" + responses: + "200": + $ref: "#/components/responses/Order" + tags: + - store + /user: + post: + operationId: createUser + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Created user object + required: true + responses: + default: + description: successful operation + tags: + - user + /user/createWithList: + post: + operationId: createUsersWithListInput + requestBody: + $ref: "#/components/requestBodies/UserArray" + tags: + - user + /user/login: + get: + operationId: loginUser + parameters: + - description: The user name for login + explode: true + in: query + name: username + required: true + schema: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + style: form + - description: The password for login in clear text + explode: true + in: query + name: password + required: true + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + type: string + description: successful operation + "400": + description: Invalid username/password supplied + tags: + - user + /user/logout: + get: + operationId: logoutUser + tags: + - user + /user/{username}: + delete: + operationId: deleteUser + parameters: + - description: The name that needs to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + tags: + - user + get: + operationId: getUserByName + parameters: + - description: The name that needs to be fetched. Use user1 for testing. + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: successful operation + tags: + - user + put: + operationId: updateUser + parameters: + - description: name that need to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Updated user object + required: true + responses: + "400": + description: Invalid user supplied + "404": + description: User not found + tags: + - user +components: + parameters: + orderId: + explode: false + in: path + name: orderId + required: true + schema: + type: string + style: simple + requestBodies: + UserArray: + content: + application/json: + schema: + items: + $ref: "#/components/schemas/User" + type: array + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: order placed for purchasing the pet + required: true + responses: + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + schemas: + Order: + description: An order for a pets from the pet store + properties: + id: + format: int64 + type: integer + petId: + format: int64 + type: integer + quantity: + format: int32 + type: integer + shipDate: + format: date-time + type: string + status: + description: Order Status + enum: + - placed + - approved + - delivered + type: string + complete: + default: false + type: boolean + title: Pet Order + type: object + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + title: Pet category + type: object + User: + description: A User who is purchasing from the pet store + properties: + id: + format: int64 + type: integer + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + description: User Status + format: int32 + type: integer + title: a User + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + status: + deprecated: true + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeQueryParams_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeQueryParams_config.yaml new file mode 100644 index 000000000000..a38e45cb6349 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeQueryParams_config.yaml @@ -0,0 +1,5 @@ +# test a combination of FILTER and REMOVE_FILTER +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_removeQueryParams.yaml +inputRules: + REMOVE_FILTER: 'queryParams:limit|status|offset ; headers: api_key|role|X-Rate-Limit ; removeVendorExtensions ; unused' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeTags_all_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeTags_all_config.yaml new file mode 100644 index 000000000000..60c9a8e3ad3d --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeTags_all_config.yaml @@ -0,0 +1,5 @@ +# remove all the tags +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_removeTags_normalized.yaml +inputRules: + REMOVE_FILTER: 'unused: tags ; removeTags' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeTags_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeTags_normalized.yaml new file mode 100644 index 000000000000..f4c4b4f2172b --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeTags_normalized.yaml @@ -0,0 +1,516 @@ +openapi: 3.0.0 +info: + description: This is a sample server Petstore server. + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: http://petstore.swagger.io/v2 +paths: + /pet: + post: + operationId: addPet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "405": + description: Invalid input + put: + externalDocs: + description: API documentation for the updatePet operation + url: http://petstore.swagger.io/v2/doc/updatePet + operationId: updatePet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + /pet/findByStatus: + get: + deprecated: true + operationId: findPetsByStatus + parameters: + - deprecated: true + explode: true + in: query + name: limit + required: false + schema: + type: integer + style: form + x-internal: true + - $ref: "#/components/parameters/offset" + - deprecated: true + description: Status values that need to be considered for filter + explode: false + in: query + name: status + required: true + schema: + items: + default: available + enum: + - available + - pending + - sold + type: string + type: array + style: form + x-role: admin + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid status value + parameters: + - deprecated: true + explode: false + in: header + name: role + required: false + schema: + type: string + style: simple + x-internal: true + /pet/findByTags: + get: + deprecated: true + operationId: findPetsByTags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + style: form + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid tag value + x-role: guest + /pet/{petId}: + delete: + description: "" + operationId: deletePet + parameters: + - explode: false + in: header + name: api_key + required: false + schema: + type: string + style: simple + - description: Pet id to delete + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "400": + description: Invalid pet value + summary: Deletes a pet + x-role: admin + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "400": + description: Invalid ID supplied + "404": + description: Pet not found + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + responses: + "405": + description: Invalid input + x-role: admin + /store/inventory: + get: + deprecated: true + operationId: getInventory + responses: + "200": + content: + application/json: + schema: + additionalProperties: + format: int32 + type: integer + type: object + description: successful operation + /store/order: + post: + operationId: placeOrder + requestBody: + $ref: "#/components/requestBodies/Order" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + "400": + description: Invalid Order + /store/order/{orderId}: + delete: + operationId: deleteOrder + parameters: + - $ref: "#/components/parameters/orderId" + get: + operationId: getOrderById + parameters: + - $ref: "#/components/parameters/orderId" + responses: + "200": + $ref: "#/components/responses/Order" + /user: + post: + operationId: createUser + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Created user object + required: true + responses: + default: + description: successful operation + /user/createWithList: + post: + operationId: createUsersWithListInput + requestBody: + $ref: "#/components/requestBodies/UserArray" + /user/login: + get: + operationId: loginUser + parameters: + - description: The user name for login + explode: true + in: query + name: username + required: true + schema: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + style: form + - description: The password for login in clear text + explode: true + in: query + name: password + required: true + schema: + type: string + style: form + responses: + "200": + content: + application/json: + schema: + type: string + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + explode: false + schema: + format: int32 + type: integer + style: simple + x-internal: true + "400": + description: Invalid username/password supplied + /user/logout: + get: + operationId: logoutUser + /user/{username}: + delete: + operationId: deleteUser + parameters: + - description: The name that needs to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + get: + operationId: getUserByName + parameters: + - description: The name that needs to be fetched. Use user1 for testing. + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: successful operation + put: + operationId: updateUser + parameters: + - description: name that need to be deleted + explode: false + in: path + name: username + required: true + schema: + type: string + style: simple + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/User" + description: Updated user object + required: true + responses: + "400": + description: Invalid user supplied + "404": + description: User not found +components: + parameters: + orderId: + explode: false + in: path + name: orderId + required: true + schema: + type: string + style: simple + offset: + deprecated: true + explode: true + in: query + name: offset + required: false + schema: + type: integer + style: form + x-internal: true + requestBodies: + UserArray: + content: + application/json: + schema: + items: + $ref: "#/components/schemas/User" + type: array + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: order placed for purchasing the pet + required: true + responses: + Order: + content: + application/json: + schema: + $ref: "#/components/schemas/Order" + description: successful operation + schemas: + Order: + description: An order for a pets from the pet store + properties: + id: + format: int64 + type: integer + petId: + format: int64 + type: integer + quantity: + format: int32 + type: integer + shipDate: + format: date-time + type: string + status: + description: Order Status + enum: + - placed + - approved + - delivered + type: string + complete: + default: false + type: boolean + title: Pet Order + type: object + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + title: Pet category + type: object + User: + description: A User who is purchasing from the pet store + properties: + id: + format: int64 + type: integer + username: + type: string + firstName: + type: string + lastName: + type: string + email: + type: string + password: + type: string + phone: + type: string + userStatus: + description: User Status + format: int32 + type: integer + title: a User + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + status: + deprecated: true + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + title: a Pet + type: object + ApiResponse: + description: Describes the result of uploading an image resource + properties: + code: + format: int32 + type: integer + type: + type: string + message: + type: string + title: An uploaded response + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_REMOVE_X_INTERNAL_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_REMOVE_X_INTERNAL_config.yaml new file mode 100644 index 000000000000..2358d596d860 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_REMOVE_X_INTERNAL_config.yaml @@ -0,0 +1,6 @@ +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_removeUnused_normalized.yaml +inputRules: + FILTER: 'tag: pet' + REMOVE_FILTER: 'internal ; unused' + REMOVE_X_INTERNAL: true diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_config.yaml new file mode 100644 index 000000000000..18bc12907b16 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_config.yaml @@ -0,0 +1,6 @@ +# test a combination of FILTER and REMOVE_FILTER +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_removeUnused_normalized.yaml +inputRules: + FILTER: 'tag: pet' + REMOVE_FILTER: 'internal ; unused ; removeVendorExtensions: x-internal' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_normalized.yaml new file mode 100644 index 000000000000..cd7f2e14ff90 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_removeUnused_normalized.yaml @@ -0,0 +1,255 @@ +openapi: 3.0.0 +info: + description: This is a sample server Petstore server. + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: http://petstore.swagger.io/v2 +tags: + - description: Everything about your Pets + name: pet +paths: + /pet: + post: + operationId: addPet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "405": + description: Invalid input + tags: + - pet + put: + externalDocs: + description: API documentation for the updatePet operation + url: http://petstore.swagger.io/v2/doc/updatePet + operationId: updatePet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + tags: + - pet + /pet/findByStatus: + get: + deprecated: true + operationId: findPetsByStatus + parameters: + - deprecated: true + description: Status values that need to be considered for filter + explode: false + in: query + name: status + required: true + schema: + items: + default: available + enum: + - available + - pending + - sold + type: string + type: array + style: form + x-role: admin + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid status value + tags: + - pet + /pet/findByTags: + get: + deprecated: true + operationId: findPetsByTags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + style: form + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid tag value + tags: + - pet + x-role: guest + /pet/{petId}: + delete: + description: "" + operationId: deletePet + parameters: + - explode: false + in: header + name: api_key + required: false + schema: + type: string + style: simple + - description: Pet id to delete + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "400": + description: Invalid pet value + summary: Deletes a pet + tags: + - pet + x-role: admin + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "400": + description: Invalid ID supplied + "404": + description: Pet not found + tags: + - pet + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + responses: + "405": + description: Invalid input + tags: + - pet + x-role: admin +components: + requestBodies: + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + schemas: + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + title: Pet category + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + status: + deprecated: true + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_tags_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_tags_config.yaml new file mode 100644 index 000000000000..4bc3c8695f6b --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_tags_config.yaml @@ -0,0 +1,5 @@ +# remove the operations having tag name user or store +inputSpec: src/test/resources/openapi_normalizer/petstore.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_tags_normalized.yaml +inputRules: + REMOVE_FILTER: 'unused; tags:user|store' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_tags_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_tags_normalized.yaml new file mode 100644 index 000000000000..6b6d6440acff --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_tags_normalized.yaml @@ -0,0 +1,286 @@ +openapi: 3.0.0 +info: + description: This is a sample server Petstore server. + version: 1.0.0 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: http://petstore.swagger.io/v2 +tags: + - description: Everything about your Pets + name: pet +paths: + /pet: + post: + operationId: addPet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "405": + description: Invalid input + tags: + - pet + put: + externalDocs: + description: API documentation for the updatePet operation + url: http://petstore.swagger.io/v2/doc/updatePet + operationId: updatePet + requestBody: + $ref: "#/components/requestBodies/Pet" + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + tags: + - pet + /pet/findByStatus: + get: + deprecated: true + operationId: findPetsByStatus + parameters: + - deprecated: true + explode: true + in: query + name: limit + required: false + schema: + type: integer + style: form + x-internal: true + - $ref: "#/components/parameters/offset" + - deprecated: true + description: Status values that need to be considered for filter + explode: false + in: query + name: status + required: true + schema: + items: + default: available + enum: + - available + - pending + - sold + type: string + type: array + style: form + x-role: admin + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid status value + tags: + - pet + parameters: + - deprecated: true + explode: false + in: header + name: role + required: false + schema: + type: string + style: simple + x-internal: true + /pet/findByTags: + get: + deprecated: true + operationId: findPetsByTags + parameters: + - description: Tags to filter by + explode: false + in: query + name: tags + required: true + schema: + items: + type: string + type: array + style: form + responses: + "200": + content: + application/json: + schema: + items: + $ref: "#/components/schemas/Pet" + type: array + description: successful operation + "400": + description: Invalid tag value + tags: + - pet + x-role: guest + /pet/{petId}: + delete: + description: "" + operationId: deletePet + parameters: + - explode: false + in: header + name: api_key + required: false + schema: + type: string + style: simple + - description: Pet id to delete + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "400": + description: Invalid pet value + summary: Deletes a pet + tags: + - pet + x-role: admin + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation + "400": + description: Invalid ID supplied + "404": + description: Pet not found + tags: + - pet + post: + operationId: updatePetWithForm + parameters: + - description: ID of pet that needs to be updated + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + requestBody: + content: + application/x-www-form-urlencoded: + schema: + properties: + name: + description: Updated name of the pet + type: string + status: + description: Updated status of the pet + type: string + type: object + responses: + "405": + description: Invalid input + tags: + - pet + x-role: admin +components: + parameters: + offset: + deprecated: true + explode: true + in: query + name: offset + required: false + schema: + type: integer + style: form + x-internal: true + requestBodies: + Pet: + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: Pet object that needs to be added to the store + required: true + schemas: + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + pattern: "^[a-zA-Z0-9]+[a-zA-Z0-9\\.\\-_]*[a-zA-Z0-9]+$" + type: string + title: Pet category + type: object + Tag: + description: A tag for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet Tag + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + example: doggie + type: string + photoUrls: + items: + type: string + type: array + tags: + items: + $ref: "#/components/schemas/Tag" + type: array + status: + deprecated: true + description: pet status in the store + enum: + - available + - pending + - sold + type: string + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_admin_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_admin_config.yaml new file mode 100644 index 000000000000..4bec7d75ba17 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_admin_config.yaml @@ -0,0 +1,5 @@ +# remove elements having x-role:admin or x-role:superuser +inputSpec: src/test/resources/openapi_normalizer/petstore_with_roles.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_with_role_admin_normalized.yaml +inputRules: + REMOVE_FILTER: 'x-role:admin|superuser ; unused' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_admin_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_admin_normalized.yaml new file mode 100644 index 000000000000..e08626c06777 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_admin_normalized.yaml @@ -0,0 +1,53 @@ +openapi: 3.0.0 +info: + version: 1.0.0 +servers: + - url: http://petstore.swagger.io/v2 +paths: + /pet/{petId}: + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation +components: + schemas: + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet category + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + type: string + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_any_config.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_any_config.yaml new file mode 100644 index 000000000000..d32c33c7084e --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_any_config.yaml @@ -0,0 +1,5 @@ +# remove elements having x-role vendor extension +inputSpec: src/test/resources/openapi_normalizer/petstore_with_roles.yaml +normalizedSpec: src/test/resources/openapi_normalizer/petstore_with_role_any_normalized.yaml +inputRules: + REMOVE_FILTER: 'x-role ; unused' diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_any_normalized.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_any_normalized.yaml new file mode 100644 index 000000000000..e08626c06777 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_role_any_normalized.yaml @@ -0,0 +1,53 @@ +openapi: 3.0.0 +info: + version: 1.0.0 +servers: + - url: http://petstore.swagger.io/v2 +paths: + /pet/{petId}: + get: + operationId: getPetById + parameters: + - description: ID of pet to return + explode: false + in: path + name: petId + required: true + schema: + format: int64 + type: integer + style: simple + responses: + "200": + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + description: successful operation +components: + schemas: + Category: + description: A category for a pet + properties: + id: + format: int64 + type: integer + name: + type: string + title: Pet category + type: object + Pet: + description: A pet for sale in the pet store + properties: + id: + format: int64 + type: integer + category: + $ref: "#/components/schemas/Category" + name: + type: string + required: + - name + - photoUrls + title: a Pet + type: object diff --git a/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_roles.yaml b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_roles.yaml new file mode 100644 index 000000000000..9f9e463b757f --- /dev/null +++ b/modules/openapi-generator/src/test/resources/openapi_normalizer/petstore_with_roles.yaml @@ -0,0 +1,171 @@ +openapi: 3.0.0 +info: + version: 1.0.0 +servers: + - url: http://petstore.swagger.io/v2 +paths: + /pet: + post: + x-role: admin + tags: + - pet + operationId: addPet + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + requestBody: + $ref: '#/components/requestBodies/Pet' + '/pet/{petId}': + get: + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + put: + x-role: superuser + requestBody: + $ref: '#/components/requestBodies/Pet' +components: + responses: + Order: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + requestBodies: + UserArray: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + description: List of user object + required: true + Pet: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + description: Pet object that needs to be added to the store + required: true + Order: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + description: order placed for purchasing the pet + required: true + schemas: + Order: + title: Pet Order + description: An order for a pets from the pet store + type: object + properties: + id: + type: integer + format: int64 + petId: + type: integer + format: int64 + complete: + type: boolean + default: false + Category: + title: Pet category + description: A category for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + User: + title: a User + description: A User who is purchasing from the pet store + type: object + properties: + id: + type: integer + format: int64 + username: + type: string + firstName: + type: string + lastName: + type: string + Tag: + title: Pet Tag + description: A tag for a pet + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + Pet: + title: a Pet + description: A pet for sale in the pet store + type: object + required: + - name + - photoUrls + properties: + id: + type: integer + format: int64 + category: + $ref: '#/components/schemas/Category' + name: + type: string + tags: + x-role: admin + type: array + items: + $ref: '#/components/schemas/Tag' + ApiResponse: + title: An uploaded response + description: Describes the result of uploading an image resource + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + parameters: + orderId: + name: orderId + in: path + required: true + schema: + type: string + offset: + in: query + name: offset + required: false + x-internal: true + deprecated: true + schema: + type: integer