Skip to content

Commit 3f8e941

Browse files
author
bnasslahsen
committed
Improve support of Webflux with Functional Endpoints
1 parent f924b27 commit 3f8e941

File tree

13 files changed

+154
-108
lines changed

13 files changed

+154
-108
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,7 @@ protected void calculatePath(HandlerMethod handlerMethod, String operationPath,
218218

219219
// RequestBody in Operation
220220
requestBuilder.getRequestBodyBuilder()
221-
.buildRequestBodyFromDoc(requestBodyDoc, methodAttributes.getClassConsumes(),
222-
methodAttributes.getMethodConsumes(), components,
221+
.buildRequestBodyFromDoc(requestBodyDoc, methodAttributes, components,
223222
methodAttributes.getJsonViewAnnotationForRequestBody())
224223
.ifPresent(operation::setRequestBody);
225224
// requests

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

Lines changed: 13 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import io.swagger.v3.core.util.AnnotationsUtils;
3838
import io.swagger.v3.oas.models.Components;
3939
import io.swagger.v3.oas.models.Operation;
40-
import io.swagger.v3.oas.models.media.ComposedSchema;
4140
import io.swagger.v3.oas.models.media.Content;
4241
import io.swagger.v3.oas.models.media.Schema;
4342
import io.swagger.v3.oas.models.responses.ApiResponse;
@@ -57,6 +56,9 @@
5756
import org.springframework.web.method.HandlerMethod;
5857

5958
import static org.springdoc.core.Constants.DEFAULT_DESCRIPTION;
59+
import static org.springdoc.core.SpringDocAnnotationsUtils.extractSchema;
60+
import static org.springdoc.core.SpringDocAnnotationsUtils.getContent;
61+
import static org.springdoc.core.SpringDocAnnotationsUtils.mergeSchema;
6062
import static org.springdoc.core.converters.ConverterUtils.isResponseTypeWrapper;
6163

6264
@SuppressWarnings("rawtypes")
@@ -160,7 +162,7 @@ public static void buildContentFromDoc(Components components, ApiResponses apiRe
160162
ApiResponse apiResponse) {
161163

162164
io.swagger.v3.oas.annotations.media.Content[] contentdoc = apiResponseAnnotations.content();
163-
Optional<Content> optionalContent = SpringDocAnnotationsUtils.getContent(contentdoc, new String[0],
165+
Optional<Content> optionalContent = getContent(contentdoc, new String[0],
164166
methodAttributes.getMethodProduces(), null, components, methodAttributes.getJsonViewAnnotation());
165167
if (apiResponsesOp.containsKey(apiResponseAnnotations.responseCode())) {
166168
// Merge with the existing content
@@ -260,7 +262,7 @@ private Type getReturnType(MethodParameter methodParameter) {
260262
}
261263

262264
public Schema calculateSchema(Components components, Type returnType, JsonView jsonView) {
263-
return !isVoid(returnType) ? SpringDocAnnotationsUtils.extractSchema(components, returnType, jsonView) : null;
265+
return !isVoid(returnType) ? extractSchema(components, returnType, jsonView) : null;
264266
}
265267

266268
private void setContent(String[] methodProduces, Content content,
@@ -280,13 +282,7 @@ private void buildApiResponses(Components components, MethodParameter methodPara
280282
else if (CollectionUtils.isEmpty(apiResponse.getContent()))
281283
apiResponse.setContent(null);
282284
if (StringUtils.isBlank(apiResponse.getDescription())) {
283-
try {
284-
HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(httpCode));
285-
apiResponse.setDescription(httpStatus.getReasonPhrase());
286-
}
287-
catch (IllegalArgumentException e) {
288-
apiResponse.setDescription(DEFAULT_DESCRIPTION);
289-
}
285+
setDescription(httpCode, apiResponse);
290286
}
291287
}
292288
if (apiResponse.getContent() != null
@@ -301,31 +297,14 @@ else if (CollectionUtils.isEmpty(apiResponse.getContent()))
301297
apiResponsesOp.addApiResponse(httpCode, apiResponse);
302298
}
303299

304-
private static void mergeSchema(Content existingContent, Schema<?> schemaN, String mediaTypeStr) {
305-
if (existingContent.containsKey(mediaTypeStr)) {
306-
io.swagger.v3.oas.models.media.MediaType mediaType = existingContent.get(mediaTypeStr);
307-
if (!schemaN.equals(mediaType.getSchema())) {
308-
// Merge the two schemas for the same mediaType
309-
Schema firstSchema = mediaType.getSchema();
310-
ComposedSchema schemaObject;
311-
if (firstSchema instanceof ComposedSchema) {
312-
schemaObject = (ComposedSchema) firstSchema;
313-
List<Schema> listOneOf = schemaObject.getOneOf();
314-
if (!CollectionUtils.isEmpty(listOneOf) && !listOneOf.contains(schemaN))
315-
schemaObject.addOneOfItem(schemaN);
316-
}
317-
else {
318-
schemaObject = new ComposedSchema();
319-
schemaObject.addOneOfItem(schemaN);
320-
schemaObject.addOneOfItem(firstSchema);
321-
}
322-
mediaType.setSchema(schemaObject);
323-
existingContent.addMediaType(mediaTypeStr, mediaType);
324-
}
300+
public static void setDescription(String httpCode, ApiResponse apiResponse) {
301+
try {
302+
HttpStatus httpStatus = HttpStatus.valueOf(Integer.valueOf(httpCode));
303+
apiResponse.setDescription(httpStatus.getReasonPhrase());
304+
}
305+
catch (IllegalArgumentException e) {
306+
apiResponse.setDescription(DEFAULT_DESCRIPTION);
325307
}
326-
else
327-
// Add the new schema for a different mediaType
328-
existingContent.addMediaType(mediaTypeStr, new io.swagger.v3.oas.models.media.MediaType().schema(schemaN));
329308
}
330309

331310
private String evaluateResponseStatus(Method method, Class<?> beanType, boolean isGeneric) {

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@
4949
import org.springframework.core.annotation.AnnotationUtils;
5050
import org.springframework.util.CollectionUtils;
5151

52-
import static org.springdoc.core.Constants.DEFAULT_DESCRIPTION;
5352
import static org.springdoc.core.Constants.DELETE_METHOD;
5453
import static org.springdoc.core.Constants.GET_METHOD;
5554
import static org.springdoc.core.Constants.HEAD_METHOD;
@@ -111,8 +110,7 @@ public OpenAPI parse(io.swagger.v3.oas.annotations.Operation apiOperation,
111110
}
112111

113112
// RequestBody in Operation
114-
requestBodyBuilder.buildRequestBodyFromDoc(apiOperation.requestBody(), methodAttributes.getClassConsumes(),
115-
methodAttributes.getMethodConsumes(), components, null).ifPresent(operation::setRequestBody);
113+
requestBodyBuilder.buildRequestBodyFromDoc(apiOperation.requestBody(), operation.getRequestBody(), methodAttributes, components).ifPresent(operation::setRequestBody);
116114

117115
// build response
118116
buildResponse(components, apiOperation, operation, methodAttributes);
@@ -340,7 +338,7 @@ private void setDescription(io.swagger.v3.oas.annotations.responses.ApiResponse
340338
apiResponseObject.setDescription(response.description());
341339
}
342340
else {
343-
apiResponseObject.setDescription(DEFAULT_DESCRIPTION);
341+
GenericResponseBuilder.setDescription(response.responseCode(), apiResponseObject);
344342
}
345343
}
346344

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

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package org.springdoc.core;
2222

23+
import java.util.Arrays;
2324
import java.util.Map;
2425
import java.util.Optional;
2526

@@ -34,6 +35,9 @@
3435
import org.springframework.core.MethodParameter;
3536
import org.springframework.web.bind.annotation.RequestPart;
3637

38+
import static org.springdoc.core.SpringDocAnnotationsUtils.mergeSchema;
39+
40+
3741
public class RequestBodyBuilder {
3842

3943
private final GenericParameterBuilder parameterBuilder;
@@ -44,8 +48,11 @@ public RequestBodyBuilder(GenericParameterBuilder parameterBuilder) {
4448
}
4549

4650
public Optional<RequestBody> buildRequestBodyFromDoc(
47-
io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, String[] classConsumes,
48-
String[] methodConsumes, Components components, JsonView jsonViewAnnotation) {
51+
io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes,
52+
Components components, JsonView jsonViewAnnotation) {
53+
String[] classConsumes = methodAttributes.getClassConsumes();
54+
String[] methodConsumes = methodAttributes.getMethodConsumes();
55+
4956
if (requestBody == null)
5057
return Optional.empty();
5158
RequestBody requestBodyObject = new RequestBody();
@@ -77,22 +84,54 @@ public Optional<RequestBody> buildRequestBodyFromDoc(
7784
if (isEmpty)
7885
return Optional.empty();
7986

80-
AnnotationsUtils
87+
Optional<Content> optionalContent = AnnotationsUtils
8188
.getContent(requestBody.content(), classConsumes == null ? new String[0] : classConsumes,
82-
methodConsumes == null ? new String[0] : methodConsumes, null, components, jsonViewAnnotation)
83-
.ifPresent(requestBodyObject::setContent);
89+
methodConsumes == null ? new String[0] : methodConsumes, null, components, jsonViewAnnotation);
90+
if (requestBodyOp == null)
91+
optionalContent.ifPresent(requestBodyObject::setContent);
92+
else {
93+
Content existingContent = requestBodyOp.getContent();
94+
if (optionalContent.isPresent() && existingContent != null) {
95+
Content newContent = optionalContent.get();
96+
if (methodAttributes.isMethodOverloaded()) {
97+
Arrays.stream(methodAttributes.getMethodProduces()).filter(mediaTypeStr -> (newContent.get(mediaTypeStr) != null)).forEach(mediaTypeStr -> mergeSchema(existingContent, newContent.get(mediaTypeStr).getSchema(), mediaTypeStr));
98+
requestBodyObject.content(existingContent);
99+
}
100+
else
101+
requestBodyObject.content(newContent);
102+
}
103+
}
104+
84105
return Optional.of(requestBodyObject);
85106
}
86107

108+
public Optional<RequestBody> buildRequestBodyFromDoc(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody,
109+
MethodAttributes methodAttributes, Components components) {
110+
return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes,
111+
components, null);
112+
}
113+
114+
public Optional<RequestBody> buildRequestBodyFromDoc(io.swagger.v3.oas.annotations.parameters.RequestBody requestBody,
115+
MethodAttributes methodAttributes, Components components,JsonView jsonViewAnnotation) {
116+
return this.buildRequestBodyFromDoc(requestBody, null, methodAttributes,
117+
components, jsonViewAnnotation);
118+
}
119+
120+
public Optional<RequestBody> buildRequestBodyFromDoc(
121+
io.swagger.v3.oas.annotations.parameters.RequestBody requestBody, RequestBody requestBodyOp, MethodAttributes methodAttributes,
122+
Components components) {
123+
return this.buildRequestBodyFromDoc(requestBody, requestBodyOp, methodAttributes,
124+
components, null);
125+
}
126+
87127
public void calculateRequestBodyInfo(Components components, MethodAttributes methodAttributes,
88128
ParameterInfo parameterInfo, RequestBodyInfo requestBodyInfo) {
89129
RequestBody requestBody = requestBodyInfo.getRequestBody();
90130
MethodParameter methodParameter = parameterInfo.getMethodParameter();
91131
// Get it from parameter level, if not present
92132
if (requestBody == null) {
93133
io.swagger.v3.oas.annotations.parameters.RequestBody requestBodyDoc = methodParameter.getParameterAnnotation(io.swagger.v3.oas.annotations.parameters.RequestBody.class);
94-
requestBody = this.buildRequestBodyFromDoc(requestBodyDoc, methodAttributes.getClassConsumes(),
95-
methodAttributes.getMethodConsumes(), components, null).orElse(null);
134+
requestBody = this.buildRequestBodyFromDoc(requestBodyDoc, methodAttributes, components).orElse(null);
96135
}
97136

98137
RequestPart requestPart = methodParameter.getParameterAnnotation(RequestPart.class);

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.lang.annotation.Annotation;
2424
import java.lang.reflect.Type;
2525
import java.util.LinkedHashMap;
26+
import java.util.List;
2627
import java.util.Map;
2728
import java.util.Optional;
2829

@@ -34,6 +35,7 @@
3435
import io.swagger.v3.oas.annotations.media.ExampleObject;
3536
import io.swagger.v3.oas.models.Components;
3637
import io.swagger.v3.oas.models.media.ArraySchema;
38+
import io.swagger.v3.oas.models.media.ComposedSchema;
3739
import io.swagger.v3.oas.models.media.Content;
3840
import io.swagger.v3.oas.models.media.MediaType;
3941
import io.swagger.v3.oas.models.media.Schema;
@@ -43,6 +45,8 @@
4345
import org.slf4j.Logger;
4446
import org.slf4j.LoggerFactory;
4547

48+
import org.springframework.util.CollectionUtils;
49+
4650
@SuppressWarnings({ "rawtypes" })
4751
public class SpringDocAnnotationsUtils extends AnnotationsUtils {
4852

@@ -126,6 +130,33 @@ public static Optional<Content> getContent(io.swagger.v3.oas.annotations.media.C
126130
return Optional.of(content);
127131
}
128132

133+
public static void mergeSchema(Content existingContent, Schema<?> schemaN, String mediaTypeStr) {
134+
if (existingContent.containsKey(mediaTypeStr)) {
135+
io.swagger.v3.oas.models.media.MediaType mediaType = existingContent.get(mediaTypeStr);
136+
if (!schemaN.equals(mediaType.getSchema())) {
137+
// Merge the two schemas for the same mediaType
138+
Schema firstSchema = mediaType.getSchema();
139+
ComposedSchema schemaObject;
140+
if (firstSchema instanceof ComposedSchema) {
141+
schemaObject = (ComposedSchema) firstSchema;
142+
List<Schema> listOneOf = schemaObject.getOneOf();
143+
if (!CollectionUtils.isEmpty(listOneOf) && !listOneOf.contains(schemaN))
144+
schemaObject.addOneOfItem(schemaN);
145+
}
146+
else {
147+
schemaObject = new ComposedSchema();
148+
schemaObject.addOneOfItem(schemaN);
149+
schemaObject.addOneOfItem(firstSchema);
150+
}
151+
mediaType.setSchema(schemaObject);
152+
existingContent.addMediaType(mediaTypeStr, mediaType);
153+
}
154+
}
155+
else
156+
// Add the new schema for a different mediaType
157+
existingContent.addMediaType(mediaTypeStr, new io.swagger.v3.oas.models.media.MediaType().schema(schemaN));
158+
}
159+
129160
private static void addEncodingToMediaType(JsonView jsonViewAnnotation, MediaType mediaType,
130161
io.swagger.v3.oas.annotations.media.Encoding[] encodings) {
131162
for (io.swagger.v3.oas.annotations.media.Encoding encoding : encodings) {

springdoc-openapi-webflux-core/src/test/resources/results/app72.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@
132132
"operationId": "findAll",
133133
"responses": {
134134
"200": {
135-
"description": "default response",
135+
"description": "OK",
136136
"content": {
137137
"application/json": {
138138
"schema": {
@@ -166,7 +166,7 @@
166166
],
167167
"responses": {
168168
"200": {
169-
"description": "default response",
169+
"description": "OK",
170170
"content": {
171171
"application/stream+json": {
172172
"schema": {
@@ -196,7 +196,7 @@
196196
},
197197
"responses": {
198198
"200": {
199-
"description": "default response",
199+
"description": "OK",
200200
"content": {
201201
"application/json": {
202202
"schema": {
@@ -227,7 +227,7 @@
227227
],
228228
"responses": {
229229
"200": {
230-
"description": "default response",
230+
"description": "OK",
231231
"content": {}
232232
}
233233
}

springdoc-openapi-webflux-core/src/test/resources/results/app73.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"operationId": "hello",
1717
"responses": {
1818
"200": {
19-
"description": "default response"
19+
"description": "OK"
2020
}
2121
}
2222
}
@@ -35,7 +35,7 @@
3535
},
3636
"responses": {
3737
"200": {
38-
"description": "default response",
38+
"description": "OK",
3939
"content": {
4040
"text/plain": {
4141
"schema": {
@@ -61,7 +61,7 @@
6161
],
6262
"responses": {
6363
"200": {
64-
"description": "default response",
64+
"description": "OK",
6565
"content": {
6666
"application/json": {
6767
"schema": {
@@ -101,4 +101,4 @@
101101
}
102102
}
103103
}
104-
}
104+
}

0 commit comments

Comments
 (0)