Skip to content

Commit 99c9751

Browse files
committed
Use Filter Parser and allow multiple filters
1 parent 42af4f9 commit 99c9751

4 files changed

Lines changed: 162 additions & 92 deletions

File tree

docs/customization.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generat
645645
646646
- `FILTER`
647647
648-
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.
648+
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 semi-column.
649649
650650
### Available Filters
651651
@@ -658,14 +658,17 @@ The `FILTER` parameter allows selective inclusion of API operations based on spe
658658
- **`tag`**
659659
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.
660660
661+
- **`path`**
662+
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.
663+
661664
### Example Usage
662665
663666
```sh
664667
java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generate \
665668
-g java \
666669
-i modules/openapi-generator/src/test/resources/3_0/petstore.yaml \
667670
-o /tmp/java-okhttp/ \
668-
--openapi-normalizer FILTER="operationId:addPet|getPetById"
671+
--openapi-normalizer FILTER="operationId:addPet|getPetById ; tag:store"
669672
```
670673

671674
- `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.

modules/openapi-generator/src/main/java/org/openapitools/codegen/OpenAPINormalizer.java

Lines changed: 93 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public class OpenAPINormalizer {
4949

5050
private TreeSet<String> anyTypeTreeSet = new TreeSet<>();
5151

52-
protected final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class);
52+
protected static final Logger LOGGER = LoggerFactory.getLogger(OpenAPINormalizer.class);
5353

5454
Set<String> ruleNames = new TreeSet<>();
5555
Set<String> rulesDefaultToTrue = new TreeSet<>();
@@ -133,10 +133,7 @@ public class OpenAPINormalizer {
133133

134134
// when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else
135135
final String FILTER = "FILTER";
136-
HashSet<String> operationIdFilters = new HashSet<>();
137-
HashSet<String> methodFilters = new HashSet<>();
138-
139-
HashSet<String> tagFilters = new HashSet<>();
136+
Filter filter = new Filter();
140137

141138
// when set (e.g. operationId:getPetById|addPet), filter out (or remove) everything else
142139
final String SET_CONTAINER_TO_NULLABLE = "SET_CONTAINER_TO_NULLABLE";
@@ -275,29 +272,11 @@ public void processRules(Map<String, String> inputRules) {
275272

276273
if (inputRules.get(FILTER) != null) {
277274
rules.put(FILTER, true);
278-
279-
String[] filterStrs = inputRules.get(FILTER).split(":");
280-
if (filterStrs.length != 2) { // only support operationId with : at the moment
281-
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));
282-
} else {
283-
if ("operationId".equals(filterStrs[0])) {
284-
operationIdFilters = Arrays.stream(filterStrs[1].split("[|]"))
285-
.filter(Objects::nonNull)
286-
.map(String::trim)
287-
.collect(Collectors.toCollection(HashSet::new));
288-
} else if ("method".equals(filterStrs[0])) {
289-
methodFilters = Arrays.stream(filterStrs[1].split("[|]"))
290-
.filter(Objects::nonNull)
291-
.map(String::trim)
292-
.collect(Collectors.toCollection(HashSet::new));
293-
} else if ("tag".equals(filterStrs[0])) {
294-
tagFilters = Arrays.stream(filterStrs[1].split("[|]"))
295-
.filter(Objects::nonNull)
296-
.map(String::trim)
297-
.collect(Collectors.toCollection(HashSet::new));
298-
} else {
299-
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));
300-
}
275+
String filters = inputRules.get(FILTER);
276+
try {
277+
filter = new Filter(filters);
278+
} catch (Exception e) {
279+
LOGGER.error("FILTER rule must be in the form of `operationId:name1|name2|name3` or `method:get|post|put` or `tag:tag1|tag2|tag3` or `path:/v1|/v2` in {}", filters);
301280
}
302281
}
303282

@@ -405,15 +384,11 @@ protected void normalizePaths() {
405384
"trace", PathItem::getTrace
406385
);
407386

408-
// Iterates over each HTTP method in methodMap, retrieves the corresponding Operation from the PathItem,
409-
// and marks it as internal (`x-internal`) if the method is not in methodFilters.
410-
methodMap.forEach((method, getter) -> {
411-
Operation operation = getter.apply(path);
412-
if (operation != null && !methodFilters.isEmpty()) {
413-
LOGGER.info("operation `{}` marked internal only (x-internal: `{}`) by the method FILTER", operation.getOperationId(), !methodFilters.contains(method));
414-
operation.addExtension("x-internal", !methodFilters.contains(method));
415-
}
416-
});
387+
if (filter.hasFilter()) {
388+
// Iterates over each HTTP method in methodMap, retrieves the corresponding Operations from the PathItem,
389+
// and marks it as internal (`x-internal=true`) if the method/operationId/tag/path is not in the filters.
390+
filter.apply(pathsEntry.getKey(), path, methodMap);
391+
}
417392

418393
// Include callback operation as well
419394
for (Operation operation : path.readOperations()) {
@@ -430,22 +405,6 @@ protected void normalizePaths() {
430405
normalizeParameters(path.getParameters());
431406

432407
for (Operation operation : operations) {
433-
if (operationIdFilters.size() > 0) {
434-
if (operationIdFilters.contains(operation.getOperationId())) {
435-
operation.addExtension(X_INTERNAL, false);
436-
} else {
437-
LOGGER.info("operation `{}` marked as internal only (x-internal: true) by the operationId FILTER", operation.getOperationId());
438-
operation.addExtension(X_INTERNAL, true);
439-
}
440-
} else if (!tagFilters.isEmpty()) {
441-
if (operation.getTags().stream().anyMatch(tagFilters::contains)) {
442-
operation.addExtension(X_INTERNAL, false);
443-
} else {
444-
LOGGER.info("operation `{}` marked as internal only (x-internal: true) by the tag FILTER", operation.getOperationId());
445-
operation.addExtension(X_INTERNAL, true);
446-
}
447-
}
448-
449408
normalizeOperation(operation);
450409
normalizeRequestBody(operation);
451410
normalizeParameters(operation.getParameters());
@@ -1349,7 +1308,7 @@ protected Schema processSimplifyOneOfEnum(Schema schema) {
13491308
*
13501309
* @param schema Schema to modify
13511310
* @param subSchemas List of sub-schemas to check
1352-
* @param schemaType Type of composed schema ("oneOf" or "anyOf")
1311+
* @param composedType Type of composed schema ("oneOf" or "anyOf")
13531312
* @return Simplified schema
13541313
*/
13551314
protected Schema simplifyComposedSchemaWithEnums(Schema schema, List<Object> subSchemas, String composedType) {
@@ -1818,4 +1777,84 @@ protected Schema processNormalize31Spec(Schema schema, Set<Schema> visitedSchema
18181777
}
18191778

18201779
// ===================== end of rules =====================
1780+
1781+
static class Filter {
1782+
protected Set<String> operationIdFilters = Collections.emptySet();
1783+
protected Set<String> methodFilters = Collections.emptySet();
1784+
protected Set<String> tagFilters = Collections.emptySet();
1785+
protected Set<String> pathStartingWithFilters = Collections.emptySet();
1786+
1787+
Filter() {
1788+
1789+
}
1790+
1791+
public Filter(String filters) {
1792+
for (String filter : filters.split(";")) {
1793+
filter = filter.trim();
1794+
String[] filterStrs = filter.split(":");
1795+
if (filterStrs.length != 2) { // only support operationId with : at the moment
1796+
throw new IllegalArgumentException("filter not supported :[" + filter + "]");
1797+
} else {
1798+
String filterKey = filterStrs[0].trim();
1799+
String filterValue = filterStrs[1];
1800+
Set<String> parsedFilters = Arrays.stream(filterValue.split("[|]"))
1801+
.filter(Objects::nonNull)
1802+
.map(String::trim)
1803+
.collect(Collectors.toCollection(HashSet::new));
1804+
if ("operationId".equals(filterKey)) {
1805+
operationIdFilters = parsedFilters;
1806+
} else if ("method".equals(filterKey)) {
1807+
methodFilters = parsedFilters;
1808+
} else if ("tag".equals(filterKey)) {
1809+
tagFilters = parsedFilters;
1810+
} else if ("path".equals(filterKey)) {
1811+
pathStartingWithFilters = parsedFilters;
1812+
} else {
1813+
throw new IllegalArgumentException("filter not supported :[" + filter + "]");
1814+
}
1815+
}
1816+
}
1817+
}
1818+
1819+
public boolean hasFilter() {
1820+
return !operationIdFilters.isEmpty() || !methodFilters.isEmpty() || !tagFilters.isEmpty() || !pathStartingWithFilters.isEmpty ();
1821+
}
1822+
1823+
public void apply(String path, PathItem pathItem, Map<String, Function<PathItem, Operation>> methodMap) {
1824+
methodMap.forEach((method, getter) -> {
1825+
Operation operation = getter.apply(pathItem);
1826+
if (operation != null) {
1827+
boolean found = false;
1828+
found |= hasMatch("path", operation, hasPathStarting(path));
1829+
found |= hasMatch("tag", operation, hasTag(operation));
1830+
found |= hasMatch("operationId", operation, hasOperationId(operation));
1831+
found |= hasMatch("method", operation, hasMethod(method));
1832+
operation.addExtension(X_INTERNAL, !found);
1833+
}
1834+
});
1835+
}
1836+
1837+
private boolean hasMatch(String filterName, Operation operation, boolean filterMatched) {
1838+
if (filterMatched) {
1839+
OpenAPINormalizer.LOGGER.info("operation `{}` marked as internal only (x-internal: true) by the {} FILTER", operation.getOperationId(), filterName);
1840+
}
1841+
return filterMatched;
1842+
}
1843+
1844+
private boolean hasPathStarting(String path) {
1845+
return pathStartingWithFilters.stream().anyMatch(filter -> path.startsWith(filter));
1846+
}
1847+
1848+
private boolean hasTag( Operation operation) {
1849+
return operation.getTags() != null && operation.getTags().stream().anyMatch(tagFilters::contains);
1850+
}
1851+
1852+
private boolean hasOperationId(Operation operation) {
1853+
return operationIdFilters.contains(operation.getOperationId());
1854+
}
1855+
1856+
private boolean hasMethod(String method) {
1857+
return methodFilters.contains(method);
1858+
}
1859+
}
18211860
}

modules/openapi-generator/src/test/java/org/openapitools/codegen/OpenAPINormalizerTest.java

Lines changed: 51 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -630,27 +630,9 @@ public void testOperationIdFilter() {
630630
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
631631
openAPINormalizer.normalize();
632632

633-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get(X_INTERNAL), false);
634-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get(X_INTERNAL), false);
635-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get(X_INTERNAL), true);
636-
}
637-
638-
@Test
639-
public void testOperationIdFilterWithTrim() {
640-
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml");
641-
642-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null);
643-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get(X_INTERNAL), true);
644-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions(), null);
645-
646-
Map<String, String> options = new HashMap<>();
647-
options.put("FILTER", "operationId:\n\t\t\t\tdelete|\n\t\tlist");
648-
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
649-
openAPINormalizer.normalize();
650-
651-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get(X_INTERNAL), false);
652-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get(X_INTERNAL), false);
653-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get(X_INTERNAL), true);
633+
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get("x-internal"), false);
634+
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get("x-internal"), false);
635+
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get("x-internal"), true);
654636
}
655637

656638
@Test
@@ -670,22 +652,56 @@ public void testFilterWithMethod() {
670652
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get(X_INTERNAL), true);
671653
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get(X_INTERNAL), true);
672654
}
673-
@Test
674-
public void testFilterWithMethodWithTrim() {
675-
OpenAPI openAPI = TestUtils.parseSpec("src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml");
676-
677-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions(), null);
678-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get(X_INTERNAL), true);
679-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions(), null);
680655

681-
Map<String, String> options = new HashMap<>();
682-
options.put("FILTER", "method:\n\t\t\t\tget");
683-
OpenAPINormalizer openAPINormalizer = new OpenAPINormalizer(openAPI, options);
684-
openAPINormalizer.normalize();
656+
@Test
657+
public void testFilterParsing() {
658+
OpenAPINormalizer.Filter filter;
659+
660+
// default
661+
filter = new OpenAPINormalizer.Filter();
662+
assertFalse(filter.hasFilter());
663+
664+
// no filter
665+
filter = new OpenAPINormalizer.Filter();
666+
assertFalse(filter.hasFilter());
667+
668+
// invalid filter
669+
assertThrows(IllegalArgumentException.class, () ->
670+
new OpenAPINormalizer.Filter("operationId:"));
671+
672+
assertThrows(IllegalArgumentException.class, () ->
673+
new OpenAPINormalizer.Filter("invalid:invalid:"));
674+
675+
// extra spaces are trimmed
676+
filter = new OpenAPINormalizer.Filter("method:\n\t\t\t\tget");
677+
assertTrue(filter.hasFilter());
678+
assertEquals(filter.methodFilters, Set.of("get"));
679+
assertTrue(filter.operationIdFilters.isEmpty());
680+
assertTrue(filter.tagFilters.isEmpty());
681+
assertTrue(filter.pathStartingWithFilters.isEmpty());
682+
683+
// multiple values separated by pipe
684+
filter = new OpenAPINormalizer.Filter("operationId:\n\t\t\t\tdelete|\n\t\tlist\t");
685+
assertTrue(filter.hasFilter());
686+
assertTrue(filter.methodFilters.isEmpty());
687+
assertEquals(filter.operationIdFilters, Set.of("delete", "list"));
688+
assertTrue(filter.tagFilters.isEmpty());
689+
assertTrue(filter.pathStartingWithFilters.isEmpty());
690+
691+
// multiple filters
692+
filter = new OpenAPINormalizer.Filter("operationId:delete|list;path:/v1");
693+
assertTrue(filter.hasFilter());
694+
assertTrue(filter.methodFilters.isEmpty());
695+
assertEquals(filter.operationIdFilters, Set.of("delete", "list"));
696+
assertTrue(filter.tagFilters.isEmpty());
697+
assertEquals(filter.pathStartingWithFilters, Set.of("/v1"));
698+
}
685699

686-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getGet().getExtensions().get(X_INTERNAL), false);
687-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getDelete().getExtensions().get(X_INTERNAL), true);
688-
assertEquals(openAPI.getPaths().get("/person/display/{personId}").getPut().getExtensions().get(X_INTERNAL), true);
700+
@Test
701+
public void testMultiFilterParsing() {
702+
OpenAPINormalizer.Filter filter = new OpenAPINormalizer.Filter("operationId: delete| list ; tag : testA |testB ");
703+
assertEquals(filter.operationIdFilters, Set.of("delete", "list"));
704+
assertEquals(filter.tagFilters, Set.of("testA", "testB"));
689705
}
690706

691707
@Test

modules/openapi-generator/src/test/resources/3_0/enableKeepOnlyFirstTagInOperation_test.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ info:
77
servers:
88
- url: http://api.example.xyz/v1
99
paths:
10+
/v1/person:
11+
get:
12+
operationId: list
13+
responses:
14+
'200':
15+
description: OK
16+
/v2/person:
17+
get:
18+
operationId: list
19+
responses:
20+
'200':
21+
description: OK
1022
/person/display/{personId}:
1123
get:
1224
tags:
@@ -83,4 +95,4 @@ components:
8395
type: object
8496
properties:
8597
test:
86-
type: string
98+
type: string

0 commit comments

Comments
 (0)