Skip to content

Commit 5c784fa

Browse files
author
bnasslahsen
committed
Improve support of Webflux with Functional Endpoints
1 parent 5d29794 commit 5c784fa

File tree

10 files changed

+444
-33
lines changed

10 files changed

+444
-33
lines changed

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

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@
5656
import org.springdoc.core.OperationBuilder;
5757
import org.springdoc.core.SpringDocConfigProperties;
5858
import org.springdoc.core.SpringDocConfigProperties.GroupConfig;
59-
import org.springdoc.core.annotations.RouterOperation;
6059
import org.springdoc.core.customizers.OpenApiCustomiser;
6160
import org.springdoc.core.customizers.OperationCustomizer;
61+
import org.springdoc.core.models.RouterOperation;
6262

6363
import org.springframework.context.ApplicationContext;
6464
import org.springframework.core.annotation.AnnotatedElementUtils;
@@ -159,7 +159,7 @@ protected synchronized OpenAPI getOpenApi() {
159159
protected abstract void getPaths(Map<String, Object> findRestControllers);
160160

161161
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
162-
Set<RequestMethod> requestMethods, String[] methodConsumes, String[] methodProduces) {
162+
Set<RequestMethod> requestMethods, String[] methodConsumes, String[] methodProduces, String[] headers) {
163163
OpenAPI openAPI = openAPIBuilder.getCalculatedOpenAPI();
164164
Components components = openAPI.getComponents();
165165
Paths paths = openAPI.getPaths();
@@ -180,7 +180,7 @@ protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
180180
RequestMapping reqMappingClass = AnnotatedElementUtils.findMergedAnnotation(handlerMethod.getBeanType(),
181181
RequestMapping.class);
182182

183-
MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces);
183+
MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces, headers);
184184
methodAttributes.setMethodOverloaded(existingOperation != null);
185185

186186
if (reqMappingClass != null) {
@@ -238,11 +238,11 @@ protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
238238
}
239239
}
240240

241-
protected void calculatePath(String operationPath, Set<RequestMethod> requestMethods, io.swagger.v3.oas.annotations.Operation apiOperation, String[] methodConsumes, String[] methodProduces) {
241+
protected void calculatePath(String operationPath, Set<RequestMethod> requestMethods, io.swagger.v3.oas.annotations.Operation apiOperation, String[] methodConsumes, String[] methodProduces, String[] headers) {
242242
OpenAPI openAPI = openAPIBuilder.getCalculatedOpenAPI();
243243
Paths paths = openAPI.getPaths();
244244
for (RequestMethod requestMethod : requestMethods) {
245-
MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces);
245+
MethodAttributes methodAttributes = new MethodAttributes(springDocConfigProperties.getDefaultConsumesMediaType(), springDocConfigProperties.getDefaultProducesMediaType(), methodConsumes, methodProduces, headers);
246246
Operation operation = new Operation();
247247
openAPI = operationParser.parse(apiOperation, operation, openAPI, methodAttributes);
248248
PathItem pathItemObject = buildPathItem(requestMethod, operation, operationPath, paths);
@@ -251,42 +251,42 @@ protected void calculatePath(String operationPath, Set<RequestMethod> requestMet
251251
}
252252

253253
protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
254-
Set<RequestMethod> requestMethods){
255-
this.calculatePath(handlerMethod, operationPath,requestMethods,null, null);
254+
Set<RequestMethod> requestMethods) {
255+
this.calculatePath(handlerMethod, operationPath, requestMethods, null, null, null);
256256
}
257257

258258
protected void calculatePath(List<RouterOperation> routerOperationList) {
259259
ApplicationContext applicationContext = openAPIBuilder.getContext();
260260
if (!CollectionUtils.isEmpty(routerOperationList)) {
261261
for (RouterOperation routerOperation : routerOperationList) {
262-
if (!Void.class.equals(routerOperation.beanClass())) {
263-
Object handlerBean = applicationContext.getBean(routerOperation.beanClass());
262+
if (!Void.class.equals(routerOperation.getBeanClass())) {
263+
Object handlerBean = applicationContext.getBean(routerOperation.getBeanClass());
264264
HandlerMethod handlerMethod = null;
265-
if (StringUtils.isNotBlank(routerOperation.beanMethod())) {
265+
if (StringUtils.isNotBlank(routerOperation.getBeanMethod())) {
266266
try {
267-
if (ArrayUtils.isEmpty(routerOperation.parameterTypes())) {
267+
if (ArrayUtils.isEmpty(routerOperation.getParameterTypes())) {
268268
Optional<Method> methodOptional = Arrays.stream(handlerBean.getClass().getDeclaredMethods())
269-
.filter(method -> routerOperation.beanMethod().equals(method.getName()) && method.getParameters().length == 0)
269+
.filter(method -> routerOperation.getBeanMethod().equals(method.getName()) && method.getParameters().length == 0)
270270
.findAny();
271271
if (!methodOptional.isPresent())
272272
methodOptional = Arrays.stream(handlerBean.getClass().getDeclaredMethods())
273-
.filter(method1 -> routerOperation.beanMethod().equals(method1.getName()))
273+
.filter(method1 -> routerOperation.getBeanMethod().equals(method1.getName()))
274274
.findAny();
275275
if (methodOptional.isPresent())
276276
handlerMethod = new HandlerMethod(handlerBean, methodOptional.get());
277277
}
278278
else
279-
handlerMethod = new HandlerMethod(handlerBean, routerOperation.beanMethod(), routerOperation.parameterTypes());
279+
handlerMethod = new HandlerMethod(handlerBean, routerOperation.getBeanMethod(), routerOperation.getParameterTypes());
280280
}
281281
catch (NoSuchMethodException e) {
282282
LOGGER.error(e.getMessage());
283283
}
284-
if (handlerMethod != null && isPackageToScan(handlerMethod.getBeanType().getPackage().getName()) && isPathToMatch(routerOperation.path()))
285-
calculatePath(handlerMethod, routerOperation.path(), new HashSet<>(Arrays.asList(routerOperation.method())), routerOperation.consumes(), routerOperation.produces());
284+
if (handlerMethod != null && isPackageToScan(handlerMethod.getBeanType().getPackage().getName()) && isPathToMatch(routerOperation.getPath()))
285+
calculatePath(handlerMethod, routerOperation.getPath(), new HashSet<>(Arrays.asList(routerOperation.getMethod())), routerOperation.getConsumes(), routerOperation.getProduces(), routerOperation.getHeaders());
286286
}
287287
}
288-
else if (StringUtils.isNotBlank(routerOperation.operation().operationId())) {
289-
calculatePath(routerOperation.path(), new HashSet<>(Arrays.asList(routerOperation.method())), routerOperation.operation(), routerOperation.consumes(), routerOperation.produces());
288+
else if (StringUtils.isNotBlank(routerOperation.getOperation().operationId())) {
289+
calculatePath(routerOperation.getPath(), new HashSet<>(Arrays.asList(routerOperation.getMethod())), routerOperation.getOperation(), routerOperation.getConsumes(), routerOperation.getProduces(), routerOperation.getHeaders());
290290
}
291291
}
292292
}

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class MethodAttributes {
4848

4949
private String defaultProducesMediaType;
5050

51-
private LinkedHashMap<String, String> headers;
51+
private LinkedHashMap<String, String> headers = new LinkedHashMap<>();
5252

5353
private String[] classProduces;
5454

@@ -64,22 +64,20 @@ public MethodAttributes(String[] methodProducesNew, String defaultConsumesMediaT
6464
this.methodProduces = methodProducesNew;
6565
this.defaultConsumesMediaType = defaultConsumesMediaType;
6666
this.defaultProducesMediaType = defaultProducesMediaType;
67-
this.headers = new LinkedHashMap<>();
6867
this.genericMapResponse = genericMapResponse;
6968
}
7069

7170
public MethodAttributes(String defaultConsumesMediaType, String defaultProducesMediaType) {
7271
this.defaultConsumesMediaType = defaultConsumesMediaType;
7372
this.defaultProducesMediaType = defaultProducesMediaType;
74-
this.headers = new LinkedHashMap<>();
7573
}
7674

77-
public MethodAttributes(String defaultConsumesMediaType, String defaultProducesMediaType, String[] methodConsumes, String[] methodProduces) {
75+
public MethodAttributes(String defaultConsumesMediaType, String defaultProducesMediaType, String[] methodConsumes, String[] methodProduces, String[] headers) {
7876
this.defaultConsumesMediaType = defaultConsumesMediaType;
7977
this.defaultProducesMediaType = defaultProducesMediaType;
8078
this.methodProduces = methodProduces;
8179
this.methodConsumes = methodConsumes;
82-
this.headers = new LinkedHashMap<>();
80+
setHeaders(headers);
8381
}
8482

8583
public String[] getClassProduces() {
@@ -159,6 +157,10 @@ else if (ArrayUtils.isNotEmpty(classConsumes))
159157
else
160158
methodConsumes = new String[] { defaultConsumesMediaType };
161159

160+
setHeaders(headers);
161+
}
162+
163+
private void setHeaders(String[] headers) {
162164
if (ArrayUtils.isNotEmpty(headers))
163165
for (String header : headers) {
164166
String[] keyValueHeader = header.split("=");

springdoc-openapi-common/src/main/java/org/springdoc/core/annotations/RouterOperation.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,13 @@
4343
* The path mapping URIs (e.g. {@code "/profile"}).
4444
* Path mapping URIs may contain placeholders (e.g. <code>"/${profile_path}"</code>).
4545
*/
46-
String path();
46+
String path() default "";
4747

4848
/**
4949
* The HTTP request methods to map to, narrowing the primary mapping:
5050
* GET, POST, HEAD, OPTIONS, PUT, PATCH, DELETE, TRACE.
5151
*/
52-
RequestMethod[] method();
52+
RequestMethod[] method() default {};
5353

5454
/**
5555
* Narrows the primary mapping by media types that can be consumed by the
@@ -78,6 +78,14 @@
7878
*/
7979
String[] produces() default {};
8080

81+
/**
82+
* The headers of the mapped request, narrowing the primary mapping.
83+
* <p>Same format for any environment: a sequence of "My-Header=myValue" style
84+
* expressions, with a request only mapped if each such header is found
85+
* to have the given value.
86+
*/
87+
String[] headers() default {};
88+
8189
/**
8290
* The class of the Handler bean.
8391
* @return the class of the Bean
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package org.springdoc.core.models;
2+
3+
import java.util.Set;
4+
5+
import org.apache.commons.lang3.StringUtils;
6+
7+
import org.springframework.http.HttpMethod;
8+
import org.springframework.util.CollectionUtils;
9+
import org.springframework.web.bind.annotation.RequestMethod;
10+
11+
public class RouterFunctionData {
12+
13+
private String path;
14+
15+
private String[] consumes;
16+
17+
private String[] headers;
18+
19+
private String queryParam;
20+
21+
private RequestMethod[] methods;
22+
23+
public RouterFunctionData(String path, String consumes, String header, String queryParam, Set<HttpMethod> methods) {
24+
this.path = path;
25+
this.queryParam = queryParam;
26+
this.methods = getMethod(methods);
27+
if (StringUtils.isNotBlank(header))
28+
this.headers = new String[] { header };
29+
if (StringUtils.isNotBlank(consumes))
30+
this.consumes = new String[] { consumes };
31+
}
32+
33+
public String getPath() {
34+
return path;
35+
}
36+
37+
public void setPath(String path) {
38+
this.path = path;
39+
}
40+
41+
public String getQueryParam() {
42+
return queryParam;
43+
}
44+
45+
public void setQueryParam(String queryParam) {
46+
this.queryParam = queryParam;
47+
}
48+
49+
public String[] getHeaders() {
50+
return headers;
51+
}
52+
53+
public void setHeaders(String[] headers) {
54+
this.headers = headers;
55+
}
56+
57+
public RequestMethod[] getMethods() {
58+
return methods;
59+
}
60+
61+
public void setMethods(RequestMethod[] methods) {
62+
this.methods = methods;
63+
}
64+
65+
public String[] getConsumes() {
66+
return consumes;
67+
}
68+
69+
public void setConsumes(String[] consumes) {
70+
this.consumes = consumes;
71+
}
72+
73+
private RequestMethod[] getMethod(Set<HttpMethod> methods) {
74+
if (!CollectionUtils.isEmpty(methods)) {
75+
return methods.stream().map(httpMethod -> getRequestMethod(httpMethod)).toArray(RequestMethod[]::new);
76+
}
77+
return null;
78+
}
79+
80+
private RequestMethod getRequestMethod(HttpMethod httpMethod) {
81+
RequestMethod requestMethod = null;
82+
switch (httpMethod) {
83+
case GET:
84+
requestMethod = RequestMethod.GET;
85+
break;
86+
case POST:
87+
requestMethod = RequestMethod.POST;
88+
break;
89+
case PUT:
90+
requestMethod = RequestMethod.PUT;
91+
break;
92+
case DELETE:
93+
requestMethod = RequestMethod.DELETE;
94+
break;
95+
case PATCH:
96+
requestMethod = RequestMethod.PATCH;
97+
break;
98+
case HEAD:
99+
requestMethod = RequestMethod.HEAD;
100+
break;
101+
case OPTIONS:
102+
requestMethod = RequestMethod.OPTIONS;
103+
break;
104+
default:
105+
// Do nothing here
106+
break;
107+
}
108+
return requestMethod;
109+
}
110+
}

0 commit comments

Comments
 (0)