diff --git a/docs/customization.md b/docs/customization.md index d74e5087b10..f79bdb6b35d 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -601,11 +601,29 @@ Example: java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml -o /tmp/java-okhttp/ --openapi-normalizer REMOVE_X_INTERNAL=true ``` -- `FILTER`: When set to `operationId:addPet|getPetById` for example, it will add `x-internal:true` to operations with operationId not equal to addPet/getPetById (which will have x-internal set to false) so that these operations marked as internal won't be generated. +- `FILTER` -Example: -``` -java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate -g java -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml -o /tmp/java-okhttp/ --openapi-normalizer FILTER="operationId:addPet|getPetById" +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. + +### Available 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`. + +- **`method`** + When set to `method:get|post`, operations **not** using `GET` or `POST` methods will be marked as internal (`x-internal: true`), preventing their generation. + +- **`tag`** + When set to `tag:person|basic`, operations **not** tagged with `person` or `basic` will be marked as internal (`x-internal: true`), and will not be generated. + +### Example Usage + +```sh +java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \ + -g java \ + -i modules/openapi-generator/src/test/resources/3_0/petstore.yaml \ + -o /tmp/java-okhttp/ \ + --openapi-normalizer FILTER="operationId:addPet|getPetById" ``` - `SET_CONTAINER_TO_NULLABLE`: When set to `array|set|map` (or just `array`) for example, it will set `nullable` in array, set and map to true. 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 9e511d10fcd..d58b6a9bcf0 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 @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; import static org.openapitools.codegen.utils.StringUtils.getUniqueString; @@ -119,6 +120,9 @@ public class OpenAPINormalizer { // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else final String FILTER = "FILTER"; HashSet operationIdFilters = new HashSet<>(); + HashSet methodFilters = new HashSet<>(); + + HashSet tagFilters = new HashSet<>(); // when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else final String SET_CONTAINER_TO_NULLABLE = "SET_CONTAINER_TO_NULLABLE"; @@ -238,15 +242,25 @@ public class OpenAPINormalizer { String[] filterStrs = inputRules.get(FILTER).split(":"); if (filterStrs.length != 2) { // only support operationId with : at the moment - LOGGER.error("FILTER rule must be in the form of `operationId:name1|name2|name3`: {}", inputRules.get(FILTER)); + LOGGER.error("FILTER rule must be in the form of `operationId:name1|name2|name3` or `method:get|post|put` or `tag:tag1|tag2|tag3`: {}", inputRules.get(FILTER)); } else { if ("operationId".equals(filterStrs[0])) { operationIdFilters = Arrays.stream(filterStrs[1].split("[|]")) .filter(Objects::nonNull) .map(String::trim) .collect(Collectors.toCollection(HashSet::new)); + } else if ("method".equals(filterStrs[0])) { + methodFilters = Arrays.stream(filterStrs[1].split("[|]")) + .filter(Objects::nonNull) + .map(String::trim) + .collect(Collectors.toCollection(HashSet::new)); + } else if ("tag".equals(filterStrs[0])) { + tagFilters = Arrays.stream(filterStrs[1].split("[|]")) + .filter(Objects::nonNull) + .map(String::trim) + .collect(Collectors.toCollection(HashSet::new)); } else { - LOGGER.error("FILTER rule must be in the form of `operationId:name1|name2|name3`: {}", inputRules.get(FILTER)); + LOGGER.error("FILTER rule must be in the form of `operationId:name1|name2|name3` or `method:get|post|put` or `tag:tag1|tag2|tag3`: {}", inputRules.get(FILTER)); } } } @@ -338,6 +352,27 @@ public class OpenAPINormalizer { 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 + ); + + // Iterates over each HTTP method in methodMap, retrieves the corresponding Operation from the PathItem, + // and marks it as internal (`x-internal`) if the method is not in methodFilters. + methodMap.forEach((method, getter) -> { + Operation operation = getter.apply(path); + if (operation != null && !methodFilters.isEmpty()) { + LOGGER.info("operation `{}` marked internal only (x-internal: `{}`) by the method FILTER", operation.getOperationId(), !methodFilters.contains(method)); + operation.addExtension("x-internal", !methodFilters.contains(method)); + } + }); + // Include callback operation as well for (Operation operation : path.readOperations()) { Map callbacks = operation.getCallbacks(); @@ -357,7 +392,14 @@ public class OpenAPINormalizer { if (operationIdFilters.contains(operation.getOperationId())) { operation.addExtension("x-internal", false); } else { - LOGGER.info("operation `{}` marked as internal only (x-internal: true) by the FILTER", operation.getOperationId()); + LOGGER.info("operation `{}` marked as internal only (x-internal: true) by the operationId FILTER", operation.getOperationId()); + operation.addExtension("x-internal", true); + } + } else if (!tagFilters.isEmpty()) { + if (operation.getTags().stream().anyMatch(tagFilters::contains)) { + operation.addExtension("x-internal", false); + } else { + LOGGER.info("operation `{}` marked as internal only (x-internal: true) by the tag FILTER", operation.getOperationId()); operation.addExtension("x-internal", true); } } 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 9569d6687c5..34b06b9819d 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 @@ -485,7 +485,7 @@ public class OpenAPINormalizerTest { } @Test - public void testFilter() { + public void testOperationIdFilter() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml"); assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null); @@ -503,7 +503,7 @@ public class OpenAPINormalizerTest { } @Test - public void testFilterWithTrim() { + public void testOperationIdFilterWithTrim() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml"); assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null); @@ -520,6 +520,76 @@ public class OpenAPINormalizerTest { assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get("x-internal"), true); } + @Test + public void testFilterWithMethod() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml"); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions(), null); + + Map options = new HashMap<>(); + options.put("FILTER", "method:get"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get("x-internal"), false); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get("x-internal"), true); + } + @Test + public void testFilterWithMethodWithTrim() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml"); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions(), null); + + Map options = new HashMap<>(); + options.put("FILTER", "method:\n\t\t\t\tget"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get("x-internal"), false); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get("x-internal"), true); + } + + @Test + public void testFilterWithTag() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml"); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions(), null); + + Map options = new HashMap<>(); + options.put("FILTER", "tag:basic"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get("x-internal"), false); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get("x-internal"), true); + } + @Test + public void testFilterWithTagWithTrim() { + OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml"); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions(), null); + + Map options = new HashMap<>(); + options.put("FILTER", "tag:basic"); + OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options); + openAPINormalizer.normalize(); + + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get("x-internal"), false); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), true); + assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get("x-internal"), true); + } + @Test public void testComposedSchemaDoesNotThrow() { OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_1/composed-schema.yaml");