Skip to content

Commit 6361201

Browse files
committed
Merge branch 'method-filtering' of https://github.com/mc1arke/springdoc-openapi into mc1arke-method-filtering
2 parents 46483c9 + 9145e15 commit 6361201

File tree

11 files changed

+439
-5
lines changed

11 files changed

+439
-5
lines changed

springdoc-openapi-common/src/main/java/org/springdoc/api/AbstractOpenApiResource.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,10 +650,27 @@ protected void getRouterFunctionPaths(String beanName, AbstractRouterFunctionVis
650650
* @return the boolean
651651
*/
652652
protected boolean isFilterCondition(HandlerMethod handlerMethod, String operationPath, String[] produces, String[] consumes, String[] headers) {
653-
return isPackageToScan(handlerMethod.getBeanType().getPackage())
653+
return isSuitableTargetMethod(handlerMethod)
654+
&& isPackageToScan(handlerMethod.getBeanType().getPackage())
654655
&& isFilterCondition(operationPath, produces, consumes, headers);
655656
}
656657

658+
/**
659+
* Is target method suitable for inclusion in current documentation/
660+
*
661+
* @param handlerMethod the method to check
662+
* @return whether the method should be included in the current OpenAPI definition
663+
*/
664+
protected boolean isSuitableTargetMethod(HandlerMethod handlerMethod) {
665+
return springDocConfigProperties.getGroupConfigs().stream()
666+
.filter(groupConfig -> this.groupName.equals(groupConfig.getGroup()))
667+
.findAny()
668+
.map(GroupConfig::getMethodFilters)
669+
.map(Collection::stream)
670+
.map(stream -> stream.allMatch(m -> m.includeMethodInOpenApi(handlerMethod.getMethod())))
671+
.orElse(true);
672+
}
673+
657674
/**
658675
* Is condition to match boolean.
659676
*

springdoc-openapi-common/src/main/java/org/springdoc/core/GroupedOpenApi.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ public class GroupedOpenApi {
8888
*/
8989
private final List<String> consumesToMatch;
9090

91+
/**
92+
* The method filters to use.
93+
*/
94+
private final List<MethodFilter> methodFilters;
95+
9196
/**
9297
* Instantiates a new Grouped open api.
9398
*
@@ -104,6 +109,7 @@ private GroupedOpenApi(Builder builder) {
104109
this.pathsToExclude = builder.pathsToExclude;
105110
this.openApiCustomisers = Objects.requireNonNull(builder.openApiCustomisers);
106111
this.operationCustomizers = Objects.requireNonNull(builder.operationCustomizers);
112+
this.methodFilters = Objects.requireNonNull(builder.methodFilters);
107113
if (CollectionUtils.isEmpty(this.pathsToMatch)
108114
&& CollectionUtils.isEmpty(this.packagesToScan)
109115
&& CollectionUtils.isEmpty(this.producesToMatch)
@@ -112,7 +118,8 @@ private GroupedOpenApi(Builder builder) {
112118
&& CollectionUtils.isEmpty(this.pathsToExclude)
113119
&& CollectionUtils.isEmpty(this.packagesToExclude)
114120
&& CollectionUtils.isEmpty(openApiCustomisers)
115-
&& CollectionUtils.isEmpty(operationCustomizers))
121+
&& CollectionUtils.isEmpty(operationCustomizers)
122+
&& CollectionUtils.isEmpty(methodFilters))
116123
throw new IllegalStateException("Packages to scan or paths to filter or openApiCustomisers/operationCustomizers can not be all null for the group:" + this.group);
117124
}
118125

@@ -215,6 +222,15 @@ public List<OperationCustomizer> getOperationCustomizers() {
215222
return operationCustomizers;
216223
}
217224

225+
/**
226+
* Gets method filters.
227+
*
228+
* @return the method filters
229+
*/
230+
public List<MethodFilter> getMethodFilters() {
231+
return methodFilters;
232+
}
233+
218234
/**
219235
* The type Builder.
220236
* @author bnasslahsen
@@ -230,6 +246,11 @@ public static class Builder {
230246
*/
231247
private final List<OperationCustomizer> operationCustomizers = new ArrayList<>();
232248

249+
/**
250+
* The methods filters to apply.
251+
*/
252+
private final List<MethodFilter> methodFilters = new ArrayList<>();
253+
233254
/**
234255
* The Group.
235256
*/
@@ -387,6 +408,17 @@ public Builder addOperationCustomizer(OperationCustomizer operationCustomizer) {
387408
return this;
388409
}
389410

411+
/**
412+
* Add method filter.
413+
*
414+
* @param methodFilter an additional filter to apply to the matched methods
415+
* @return the builder
416+
*/
417+
public Builder addMethodFilter(MethodFilter methodFilter) {
418+
this.methodFilters.add(methodFilter);
419+
return this;
420+
}
421+
390422
/**
391423
* Build grouped open api.
392424
*
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
*
3+
* *
4+
* * * Copyright 2019-2020 the original author or authors.
5+
* * *
6+
* * * Licensed under the Apache License, Version 2.0 (the "License");
7+
* * * you may not use this file except in compliance with the License.
8+
* * * You may obtain a copy of the License at
9+
* * *
10+
* * * https://www.apache.org/licenses/LICENSE-2.0
11+
* * *
12+
* * * Unless required by applicable law or agreed to in writing, software
13+
* * * distributed under the License is distributed on an "AS IS" BASIS,
14+
* * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* * * See the License for the specific language governing permissions and
16+
* * * limitations under the License.
17+
* *
18+
*
19+
*/
20+
21+
package org.springdoc.core;
22+
23+
import java.lang.reflect.Method;
24+
25+
/**
26+
* A filter to allow conditionally including any detected methods in an OpenApi definition.
27+
* @author michael.clarke
28+
*/
29+
@FunctionalInterface
30+
public interface MethodFilter {
31+
32+
/**
33+
* Whether the given method should be included in the generated OpenApi definitions. Only methods from classes
34+
* detected by the relevant loader will be passed to this filter; it cannot be used to load methods that are not
35+
* annotated with `RequestMethod` or similar mechanisms. Methods that are rejected by this filter will not be
36+
* processed any further, although methods accepted by this filter may still be rejected by other checks, such as
37+
* package inclusion checks so may still be excluded from the final OpenApi definition.
38+
*
39+
* @param method the method to perform checks against
40+
* @return whether this method should be used for further processing
41+
*/
42+
boolean includeMethodInOpenApi(Method method);
43+
}

springdoc-openapi-common/src/main/java/org/springdoc/core/SpringDocConfigProperties.java

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,11 @@ public static class GroupConfig {
10811081
*/
10821082
private List<String> consumesToMatch;
10831083

1084+
/**
1085+
* The method filters to use.
1086+
*/
1087+
private List<MethodFilter> methodFilters;
1088+
10841089
/**
10851090
* Instantiates a new Group config.
10861091
*/
@@ -1098,10 +1103,32 @@ public GroupConfig() {
10981103
* @param producesToMatch the produces to match
10991104
* @param consumesToMatch the consumes to match
11001105
* @param headersToMatch the headers to match
1106+
* @deprecated Use {@link #GroupConfig(String, List, List, List, List, List, List, List, List)}
11011107
*/
1108+
@Deprecated
11021109
public GroupConfig(String group, List<String> pathsToMatch, List<String> packagesToScan,
11031110
List<String> packagesToExclude, List<String> pathsToExclude,
1104-
List<String> producesToMatch,List<String> consumesToMatch,List<String> headersToMatch) {
1111+
List<String> producesToMatch, List<String> consumesToMatch, List<String> headersToMatch) {
1112+
this(group, pathsToMatch, packagesToScan, packagesToExclude, pathsToExclude, producesToMatch, consumesToMatch, headersToMatch, new ArrayList<>());
1113+
}
1114+
1115+
/**
1116+
* Instantiates a new Group config.
1117+
*
1118+
* @param group the group
1119+
* @param pathsToMatch the paths to match
1120+
* @param packagesToScan the packages to scan
1121+
* @param packagesToExclude the packages to exclude
1122+
* @param pathsToExclude the paths to exclude
1123+
* @param producesToMatch the produces to match
1124+
* @param consumesToMatch the consumes to match
1125+
* @param headersToMatch the headers to match
1126+
* @param methodFilters the method filters to use
1127+
*/
1128+
public GroupConfig(String group, List<String> pathsToMatch, List<String> packagesToScan,
1129+
List<String> packagesToExclude, List<String> pathsToExclude,
1130+
List<String> producesToMatch, List<String> consumesToMatch, List<String> headersToMatch,
1131+
List<MethodFilter> methodFilters) {
11051132
this.pathsToMatch = pathsToMatch;
11061133
this.pathsToExclude = pathsToExclude;
11071134
this.packagesToExclude = packagesToExclude;
@@ -1110,6 +1137,7 @@ public GroupConfig(String group, List<String> pathsToMatch, List<String> package
11101137
this.producesToMatch = producesToMatch;
11111138
this.consumesToMatch = consumesToMatch;
11121139
this.headersToMatch = headersToMatch;
1140+
this.methodFilters = methodFilters;
11131141
}
11141142

11151143
/**
@@ -1255,5 +1283,23 @@ public List<String> getProducesToMatch() {
12551283
public void setProducesToMatch(List<String> producesToMatch) {
12561284
this.producesToMatch = producesToMatch;
12571285
}
1286+
1287+
/**
1288+
* Gets the method filters to use.
1289+
*
1290+
* @return the method filters to use
1291+
*/
1292+
public List<MethodFilter> getMethodFilters() {
1293+
return methodFilters;
1294+
}
1295+
1296+
/**
1297+
* Sets the method filters to use.
1298+
*
1299+
* @param methodFilters the method filters to use
1300+
*/
1301+
public void setMethodFilters(List<MethodFilter> methodFilters) {
1302+
this.methodFilters = methodFilters;
1303+
}
12581304
}
12591305
}

springdoc-openapi-webflux-core/src/main/java/org/springdoc/webflux/api/MultipleOpenApiResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public void afterPropertiesSet() {
116116
this.groupedOpenApiResources = groupedOpenApis.stream()
117117
.collect(Collectors.toMap(GroupedOpenApi::getGroup, item ->
118118
{
119-
GroupConfig groupConfig = new GroupConfig(item.getGroup(), item.getPathsToMatch(), item.getPackagesToScan(), item.getPackagesToExclude(), item.getPathsToExclude(), item.getProducesToMatch(), item.getConsumesToMatch(),item.getHeadersToMatch());
119+
GroupConfig groupConfig = new GroupConfig(item.getGroup(), item.getPathsToMatch(), item.getPackagesToScan(), item.getPackagesToExclude(), item.getPathsToExclude(), item.getProducesToMatch(), item.getConsumesToMatch(),item.getHeadersToMatch(), item.getMethodFilters());
120120
springDocConfigProperties.addGroupConfig(groupConfig);
121121
return buildWebFluxOpenApiResource(item);
122122
}

springdoc-openapi-webmvc-core/src/main/java/org/springdoc/webmvc/api/MultipleOpenApiResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public void afterPropertiesSet() {
115115
this.groupedOpenApiResources = groupedOpenApis.stream()
116116
.collect(Collectors.toMap(GroupedOpenApi::getGroup, item ->
117117
{
118-
GroupConfig groupConfig = new GroupConfig(item.getGroup(), item.getPathsToMatch(), item.getPackagesToScan(), item.getPackagesToExclude(), item.getPathsToExclude(), item.getProducesToMatch(), item.getConsumesToMatch(), item.getHeadersToMatch());
118+
GroupConfig groupConfig = new GroupConfig(item.getGroup(), item.getPathsToMatch(), item.getPackagesToScan(), item.getPackagesToExclude(), item.getPathsToExclude(), item.getProducesToMatch(), item.getConsumesToMatch(), item.getHeadersToMatch(), item.getMethodFilters());
119119
springDocConfigProperties.addGroupConfig(groupConfig);
120120
return buildWebMvcOpenApiResource(item);
121121
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package test.org.springdoc.api.app177;
2+
3+
import java.lang.annotation.Retention;
4+
import java.lang.annotation.RetentionPolicy;
5+
6+
import org.springdoc.core.GroupedOpenApi;
7+
import org.springframework.context.annotation.Bean;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
import org.springframework.web.bind.annotation.PostMapping;
10+
import org.springframework.web.bind.annotation.PutMapping;
11+
import org.springframework.web.bind.annotation.RestController;
12+
13+
@RestController
14+
public class AnnotatedController {
15+
16+
@Group1
17+
@GetMapping("/annotated")
18+
public String annotatedGet() {
19+
return "annotated";
20+
}
21+
22+
@Group1
23+
@PostMapping("/annotated")
24+
public String annotatedPost() {
25+
return "annotated";
26+
}
27+
28+
@Group2
29+
@PutMapping("/annotated")
30+
public String annotatedPut() {
31+
return "annotated";
32+
}
33+
34+
@Bean
35+
public GroupedOpenApi group1OpenApi() {
36+
return GroupedOpenApi.builder()
37+
.group("annotatedGroup1")
38+
.addMethodFilter(method -> method.isAnnotationPresent(Group1.class))
39+
.build();
40+
}
41+
42+
@Bean
43+
public GroupedOpenApi group2OpenApi() {
44+
return GroupedOpenApi.builder()
45+
.group("annotatedGroup2")
46+
.addMethodFilter(method -> method.isAnnotationPresent(Group2.class))
47+
.build();
48+
}
49+
50+
@Bean
51+
public GroupedOpenApi group3OpenApi() {
52+
return GroupedOpenApi.builder()
53+
.group("annotatedCombinedGroup")
54+
.addMethodFilter(method -> method.isAnnotationPresent(Group1.class) || method.isAnnotationPresent(Group2.class))
55+
.build();
56+
}
57+
58+
@Retention(RetentionPolicy.RUNTIME)
59+
@interface Group1 {
60+
61+
}
62+
63+
@Retention(RetentionPolicy.RUNTIME)
64+
@interface Group2 {
65+
66+
}
67+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
*
3+
* *
4+
* * *
5+
* * * * Copyright 2019-2020 the original author or authors.
6+
* * * *
7+
* * * * Licensed under the Apache License, Version 2.0 (the "License");
8+
* * * * you may not use this file except in compliance with the License.
9+
* * * * You may obtain a copy of the License at
10+
* * * *
11+
* * * * https://www.apache.org/licenses/LICENSE-2.0
12+
* * * *
13+
* * * * Unless required by applicable law or agreed to in writing, software
14+
* * * * distributed under the License is distributed on an "AS IS" BASIS,
15+
* * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* * * * See the License for the specific language governing permissions and
17+
* * * * limitations under the License.
18+
* * *
19+
* *
20+
*
21+
*
22+
*/
23+
24+
package test.org.springdoc.api.app177;
25+
26+
import static org.hamcrest.Matchers.is;
27+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
28+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
29+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
30+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
31+
32+
import org.junit.jupiter.api.Test;
33+
import org.springdoc.core.Constants;
34+
import org.springframework.boot.autoconfigure.SpringBootApplication;
35+
36+
import test.org.springdoc.api.AbstractSpringDocTest;
37+
38+
class SpringDocApp177Test extends AbstractSpringDocTest {
39+
40+
@SpringBootApplication
41+
static class SpringDocTestApp {}
42+
43+
@Test
44+
void testFilterOnlyPicksUpMatchedMethods() throws Exception {
45+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/annotatedGroup1"))
46+
.andExpect(status().isOk())
47+
.andExpect(jsonPath("$.openapi", is("3.0.1")))
48+
.andExpect(content().json(getContent("results/app177-1.json"), true));
49+
}
50+
51+
@Test
52+
void testFilterOnlyPicksUpMatchedMethodsWithDifferentFilter() throws Exception {
53+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/annotatedGroup2"))
54+
.andExpect(status().isOk())
55+
.andExpect(jsonPath("$.openapi", is("3.0.1")))
56+
.andExpect(content().json(getContent("results/app177-2.json"), true));
57+
}
58+
59+
@Test
60+
void testFilterOnlyPicksUpCombinedMatchedMethods() throws Exception {
61+
mockMvc.perform(get(Constants.DEFAULT_API_DOCS_URL + "/annotatedCombinedGroup"))
62+
.andExpect(status().isOk())
63+
.andExpect(jsonPath("$.openapi", is("3.0.1")))
64+
.andExpect(content().json(getContent("results/app177.json"), true));
65+
}
66+
67+
}

0 commit comments

Comments
 (0)