Skip to content

Commit 97fdec3

Browse files
author
bnasslahsen
committed
Use required attribute from spring RequestBody annotation. Fixes #468
1 parent 5c0a7b5 commit 97fdec3

File tree

17 files changed

+454
-243
lines changed

17 files changed

+454
-243
lines changed

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

Lines changed: 60 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@
4646
import io.swagger.v3.oas.models.Components;
4747
import io.swagger.v3.oas.models.OpenAPI;
4848
import io.swagger.v3.oas.models.Operation;
49+
import io.swagger.v3.oas.models.media.Content;
50+
import io.swagger.v3.oas.models.media.MediaType;
4951
import io.swagger.v3.oas.models.media.Schema;
5052
import io.swagger.v3.oas.models.parameters.Parameter;
53+
import io.swagger.v3.oas.models.parameters.RequestBody;
5154
import org.apache.commons.lang3.StringUtils;
5255
import org.springdoc.core.customizers.OperationCustomizer;
5356
import org.springdoc.core.customizers.ParameterCustomizer;
@@ -81,7 +84,7 @@ public abstract class AbstractRequestBuilder {
8184
private static final List<Class> PARAM_TYPES_TO_IGNORE = new ArrayList<>();
8285

8386
// using string litterals to support both validation-api v1 and v2
84-
private static final String[] ANNOTATIONS_FOR_REQUIRED = { NotNull.class.getName(), "javax.validation.constraints.NotBlank", "javax.validation.constraints.NotEmpty" };
87+
private static final String[] ANNOTATIONS_FOR_REQUIRED = { NotNull.class.getName(), org.springframework.web.bind.annotation.RequestBody.class.getName(),"javax.validation.constraints.NotBlank", "javax.validation.constraints.NotEmpty" };
8588

8689
private static final String POSITIVE_OR_ZERO = "javax.validation.constraints.PositiveOrZero";
8790

@@ -183,6 +186,7 @@ else if (!RequestMethod.GET.equals(requestMethod)) {
183186
requestBodyInfo.setRequestBody(operation.getRequestBody());
184187
requestBodyBuilder.calculateRequestBodyInfo(components, handlerMethod, methodAttributes, i,
185188
parameterInfo, requestBodyInfo);
189+
applyBeanValidatorAnnotations(requestBodyInfo.getRequestBody(), Arrays.asList(parameters[i].getAnnotations()));
186190
}
187191
customiseParameter(parameter, parameterInfo, handlerMethod);
188192
}
@@ -349,54 +353,26 @@ private Parameter buildParam(String in, Components components, ParameterInfo par
349353

350354
private void applyBeanValidatorAnnotations(final Parameter parameter, final List<Annotation> annotations) {
351355
Map<String, Annotation> annos = new HashMap<>();
352-
if (annotations != null) {
356+
if (annotations != null)
353357
annotations.forEach(annotation -> annos.put(annotation.annotationType().getName(), annotation));
354-
}
355-
356358
boolean annotationExists = Arrays.stream(ANNOTATIONS_FOR_REQUIRED).anyMatch(annos::containsKey);
357-
358-
if (annotationExists) {
359+
if (annotationExists)
359360
parameter.setRequired(true);
360-
}
361-
362361
Schema<?> schema = parameter.getSchema();
362+
applyValidationsToSchema(annos, schema);
363+
}
363364

364-
if (annos.containsKey(Min.class.getName())) {
365-
Min min = (Min) annos.get(Min.class.getName());
366-
schema.setMinimum(BigDecimal.valueOf(min.value()));
367-
}
368-
if (annos.containsKey(Max.class.getName())) {
369-
Max max = (Max) annos.get(Max.class.getName());
370-
schema.setMaximum(BigDecimal.valueOf(max.value()));
371-
}
372-
calculateSize(annos, schema);
373-
if (annos.containsKey(DecimalMin.class.getName())) {
374-
DecimalMin min = (DecimalMin) annos.get(DecimalMin.class.getName());
375-
if (min.inclusive()) {
376-
schema.setMinimum(BigDecimal.valueOf(Double.parseDouble(min.value())));
377-
}
378-
else {
379-
schema.setExclusiveMinimum(!min.inclusive());
380-
}
381-
}
382-
if (annos.containsKey(DecimalMax.class.getName())) {
383-
DecimalMax max = (DecimalMax) annos.get(DecimalMax.class.getName());
384-
if (max.inclusive()) {
385-
schema.setMaximum(BigDecimal.valueOf(Double.parseDouble(max.value())));
386-
}
387-
else {
388-
schema.setExclusiveMaximum(!max.inclusive());
389-
}
390-
}
391-
if (annos.containsKey(POSITIVE_OR_ZERO)) {
392-
schema.setMinimum(BigDecimal.ZERO);
393-
}
394-
if (annos.containsKey(NEGATIVE_OR_ZERO)) {
395-
schema.setMaximum(BigDecimal.ZERO);
396-
}
397-
if (annos.containsKey(Pattern.class.getName())) {
398-
Pattern pattern = (Pattern) annos.get(Pattern.class.getName());
399-
schema.setPattern(pattern.regexp());
365+
private void applyBeanValidatorAnnotations(final RequestBody requestBody, final List<Annotation> annotations) {
366+
Map<String, Annotation> annos = new HashMap<>();
367+
if (annotations != null)
368+
annotations.forEach(annotation -> annos.put(annotation.annotationType().getName(), annotation));
369+
boolean annotationExists = Arrays.stream(ANNOTATIONS_FOR_REQUIRED).anyMatch(annos::containsKey);
370+
if (annotationExists)
371+
requestBody.setRequired(true);
372+
Content content = requestBody.getContent();
373+
for (MediaType mediaType : content.values()) {
374+
Schema schema = mediaType.getSchema();
375+
applyValidationsToSchema(annos, schema);
400376
}
401377
}
402378

@@ -451,6 +427,46 @@ private Map<String, io.swagger.v3.oas.annotations.Parameter> getApiParameters(Me
451427
return apiParametersMap;
452428
}
453429

430+
private void applyValidationsToSchema(Map<String, Annotation> annos, Schema<?> schema) {
431+
if (annos.containsKey(Min.class.getName())) {
432+
Min min = (Min) annos.get(Min.class.getName());
433+
schema.setMinimum(BigDecimal.valueOf(min.value()));
434+
}
435+
if (annos.containsKey(Max.class.getName())) {
436+
Max max = (Max) annos.get(Max.class.getName());
437+
schema.setMaximum(BigDecimal.valueOf(max.value()));
438+
}
439+
calculateSize(annos, schema);
440+
if (annos.containsKey(DecimalMin.class.getName())) {
441+
DecimalMin min = (DecimalMin) annos.get(DecimalMin.class.getName());
442+
if (min.inclusive()) {
443+
schema.setMinimum(BigDecimal.valueOf(Double.parseDouble(min.value())));
444+
}
445+
else {
446+
schema.setExclusiveMinimum(!min.inclusive());
447+
}
448+
}
449+
if (annos.containsKey(DecimalMax.class.getName())) {
450+
DecimalMax max = (DecimalMax) annos.get(DecimalMax.class.getName());
451+
if (max.inclusive()) {
452+
schema.setMaximum(BigDecimal.valueOf(Double.parseDouble(max.value())));
453+
}
454+
else {
455+
schema.setExclusiveMaximum(!max.inclusive());
456+
}
457+
}
458+
if (annos.containsKey(POSITIVE_OR_ZERO)) {
459+
schema.setMinimum(BigDecimal.ZERO);
460+
}
461+
if (annos.containsKey(NEGATIVE_OR_ZERO)) {
462+
schema.setMaximum(BigDecimal.ZERO);
463+
}
464+
if (annos.containsKey(Pattern.class.getName())) {
465+
Pattern pattern = (Pattern) annos.get(Pattern.class.getName());
466+
schema.setPattern(pattern.regexp());
467+
}
468+
}
469+
454470
public static void addRequestWrapperToIgnore(Class<?>... classes) {
455471
PARAM_TYPES_TO_IGNORE.addAll(Arrays.asList(classes));
456472
}

springdoc-openapi-data-rest/src/test/resources/application-test.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@
1616
# */
1717
#
1818
spring.main.banner-mode:"off"
19-
logging.level.root=OFF
19+
logging.level.root=ERROR
20+
logging.level.test.org.springdoc.api=ERROR

springdoc-openapi-data-rest/src/test/resources/logback-test.xml

Lines changed: 0 additions & 22 deletions
This file was deleted.

springdoc-openapi-data-rest/src/test/resources/results/app4.json

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -11,111 +11,113 @@
1111
}
1212
],
1313
"paths": {
14-
"/employees": {
14+
"/employees/{id}": {
1515
"get": {
1616
"tags": [
1717
"employee-controller"
1818
],
19-
"operationId": "findAll",
19+
"operationId": "findOne",
20+
"parameters": [
21+
{
22+
"name": "id",
23+
"in": "path",
24+
"required": true,
25+
"schema": {
26+
"type": "integer",
27+
"format": "int64"
28+
}
29+
}
30+
],
2031
"responses": {
2132
"200": {
2233
"description": "default response",
2334
"content": {
2435
"*/*": {
2536
"schema": {
26-
"$ref": "#/components/schemas/CollectionModelEntityModelEmployee"
37+
"$ref": "#/components/schemas/EntityModelEmployee"
2738
}
2839
}
2940
}
3041
}
3142
}
3243
},
33-
"post": {
44+
"put": {
3445
"tags": [
3546
"employee-controller"
3647
],
37-
"operationId": "newEmployee",
48+
"operationId": "updateEmployee",
49+
"parameters": [
50+
{
51+
"name": "id",
52+
"in": "path",
53+
"required": true,
54+
"schema": {
55+
"type": "integer",
56+
"format": "int64"
57+
}
58+
}
59+
],
3860
"requestBody": {
3961
"content": {
4062
"application/json": {
4163
"schema": {
4264
"$ref": "#/components/schemas/Employee"
4365
}
4466
}
45-
}
67+
},
68+
"required": true
4669
},
4770
"responses": {
4871
"200": {
49-
"description": "default response",
50-
"content": {
51-
"*/*": {
52-
"schema": {
53-
"$ref": "#/components/schemas/EntityModelEmployee"
54-
}
55-
}
56-
}
72+
"description": "default response"
5773
}
5874
}
5975
}
6076
},
61-
"/employees/{id}": {
77+
"/employees": {
6278
"get": {
6379
"tags": [
6480
"employee-controller"
6581
],
66-
"operationId": "findOne",
67-
"parameters": [
68-
{
69-
"name": "id",
70-
"in": "path",
71-
"required": true,
72-
"schema": {
73-
"type": "integer",
74-
"format": "int64"
75-
}
76-
}
77-
],
82+
"operationId": "findAll",
7883
"responses": {
7984
"200": {
8085
"description": "default response",
8186
"content": {
8287
"*/*": {
8388
"schema": {
84-
"$ref": "#/components/schemas/EntityModelEmployee"
89+
"$ref": "#/components/schemas/CollectionModelEntityModelEmployee"
8590
}
8691
}
8792
}
8893
}
8994
}
9095
},
91-
"put": {
96+
"post": {
9297
"tags": [
9398
"employee-controller"
9499
],
95-
"operationId": "updateEmployee",
96-
"parameters": [
97-
{
98-
"name": "id",
99-
"in": "path",
100-
"required": true,
101-
"schema": {
102-
"type": "integer",
103-
"format": "int64"
104-
}
105-
}
106-
],
100+
"operationId": "newEmployee",
107101
"requestBody": {
108102
"content": {
109103
"application/json": {
110104
"schema": {
111105
"$ref": "#/components/schemas/Employee"
112106
}
113107
}
114-
}
108+
},
109+
"required": true
115110
},
116111
"responses": {
117112
"200": {
118-
"description": "default response"
113+
"description": "default response",
114+
"content": {
115+
"*/*": {
116+
"schema": {
117+
"$ref": "#/components/schemas/EntityModelEmployee"
118+
}
119+
}
120+
}
119121
}
120122
}
121123
}

springdoc-openapi-security/src/test/resources/results/app1.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@
2828
"$ref": "#/components/schemas/Person"
2929
}
3030
}
31-
}
31+
},
32+
"required": true
3233
},
3334
"responses": {
3435
"200": {
@@ -57,7 +58,8 @@
5758
"$ref": "#/components/schemas/Person"
5859
}
5960
}
60-
}
61+
},
62+
"required": true
6163
},
6264
"responses": {
6365
"200": {

0 commit comments

Comments
 (0)