Skip to content

Commit 331e3e0

Browse files
author
bnasslahsen
committed
Unexpected fields in request body definition for RepresentationModel DTO (HATEOAS). fixes #725
1 parent 0f79c90 commit 331e3e0

File tree

9 files changed

+316
-4
lines changed

9 files changed

+316
-4
lines changed

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ public class MethodAttributes {
5252
*/
5353
private boolean withApiResponseDoc;
5454

55+
/**
56+
* The With response body schema doc.
57+
*/
58+
private boolean withResponseBodySchemaDoc;
59+
5560
/**
5661
* The Json view annotation.
5762
*/
@@ -386,4 +391,21 @@ public Map<String, ApiResponse> getGenericMapResponse() {
386391
return genericMapResponse;
387392
}
388393

394+
/**
395+
* Is with response body schema doc boolean.
396+
*
397+
* @return the boolean
398+
*/
399+
public boolean isWithResponseBodySchemaDoc() {
400+
return withResponseBodySchemaDoc;
401+
}
402+
403+
/**
404+
* Sets with response body schema doc.
405+
*
406+
* @param withResponseBodySchemaDoc the with response body schema doc
407+
*/
408+
public void setWithResponseBodySchemaDoc(boolean withResponseBodySchemaDoc) {
409+
this.withResponseBodySchemaDoc = withResponseBodySchemaDoc;
410+
}
389411
}

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

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,14 @@ private void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.R
127127
Optional<Content> optionalContent = AnnotationsUtils
128128
.getContent(requestBody.content(), getConsumes(classConsumes),
129129
getConsumes(methodConsumes), null, components, jsonViewAnnotation);
130-
if (requestBodyOp == null)
131-
optionalContent.ifPresent(requestBodyObject::setContent);
130+
if (requestBodyOp == null) {
131+
if (optionalContent.isPresent()) {
132+
Content content = optionalContent.get();
133+
requestBodyObject.setContent(content);
134+
if (containsResponseBodySchema(content))
135+
methodAttributes.setWithResponseBodySchemaDoc(true);
136+
}
137+
}
132138
else {
133139
Content existingContent = requestBodyOp.getContent();
134140
if (optionalContent.isPresent() && existingContent != null) {
@@ -146,6 +152,10 @@ private void buildResquestBodyContent(io.swagger.v3.oas.annotations.parameters.R
146152
}
147153
}
148154

155+
private boolean containsResponseBodySchema(Content content) {
156+
return content.entrySet().stream().anyMatch(stringMediaTypeEntry -> stringMediaTypeEntry.getValue().getSchema() != null);
157+
}
158+
149159
/**
150160
* Get consumes string [ ].
151161
*
@@ -254,7 +264,7 @@ private RequestBody buildRequestBody(RequestBody requestBody, Components compone
254264
methodAttributes.getJsonViewAnnotationForRequestBody());
255265
buildContent(requestBody, methodAttributes, schema);
256266
}
257-
else {
267+
else if (!methodAttributes.isWithResponseBodySchemaDoc()) {
258268
Schema<?> schema = parameterBuilder.calculateSchema(components, parameterInfo, requestBodyInfo,
259269
methodAttributes.getJsonViewAnnotationForRequestBody());
260270
mergeContent(requestBody, methodAttributes, schema);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
*
3+
* * Copyright 2019-2020 the original author or authors.
4+
* *
5+
* * Licensed under the Apache License, Version 2.0 (the "License");
6+
* * you may not use this file except in compliance with the License.
7+
* * You may obtain a copy of the License at
8+
* *
9+
* * https://www.apache.org/licenses/LICENSE-2.0
10+
* *
11+
* * Unless required by applicable law or agreed to in writing, software
12+
* * distributed under the License is distributed on an "AS IS" BASIS,
13+
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* * See the License for the specific language governing permissions and
15+
* * limitations under the License.
16+
*
17+
*/
18+
19+
package test.org.springdoc.api.app5;
20+
21+
import io.swagger.v3.oas.models.Components;
22+
import io.swagger.v3.oas.models.OpenAPI;
23+
import io.swagger.v3.oas.models.media.Schema;
24+
import io.swagger.v3.oas.models.media.StringSchema;
25+
import test.org.springdoc.api.AbstractSpringDocTest;
26+
27+
import org.springframework.boot.autoconfigure.SpringBootApplication;
28+
import org.springframework.context.annotation.Bean;
29+
30+
public class SpringDocApp5Test extends AbstractSpringDocTest {
31+
32+
@SpringBootApplication
33+
static class SpringDocTestApp {
34+
@Bean
35+
public OpenAPI customOpenAPI() {
36+
return new OpenAPI().components(new Components()
37+
.addSchemas("CompanyDtoNew",new Schema().addProperties("id", new StringSchema().format("uuid"
38+
)).addProperties("name", new StringSchema())));
39+
}
40+
}
41+
42+
43+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package test.org.springdoc.api.app5.controller;
2+
3+
import javax.validation.Valid;
4+
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import lombok.RequiredArgsConstructor;
8+
import test.org.springdoc.api.app5.entities.CompanyDto;
9+
10+
import org.springframework.http.MediaType;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestBody;
13+
import org.springframework.web.bind.annotation.RestController;
14+
15+
/**
16+
* @author Davide Pedone
17+
* 2020
18+
*/
19+
@RestController
20+
@RequiredArgsConstructor
21+
public class CompanyController {
22+
23+
24+
@PostMapping
25+
@io.swagger.v3.oas.annotations.parameters.RequestBody(
26+
description = "Details of the Item to be created",
27+
content = @Content(schema = @Schema(ref = "#/components/schemas/CompanyDtoNew")))
28+
public CompanyDto create(@Valid @RequestBody final CompanyDto companyDto) {
29+
30+
return null;
31+
}
32+
33+
34+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package test.org.springdoc.api.app5.entities;
2+
3+
import java.util.UUID;
4+
5+
import javax.validation.constraints.NotNull;
6+
7+
import com.fasterxml.jackson.annotation.JsonProperty;
8+
import lombok.AllArgsConstructor;
9+
import lombok.Builder;
10+
import lombok.Data;
11+
import lombok.EqualsAndHashCode;
12+
13+
import org.springframework.hateoas.RepresentationModel;
14+
import org.springframework.hateoas.server.core.Relation;
15+
16+
/**
17+
* @author bnasslahsen
18+
*/
19+
@AllArgsConstructor
20+
@Data
21+
@Builder
22+
@EqualsAndHashCode(callSuper = false)
23+
@Relation(collectionRelation = "companies", itemRelation = "company")
24+
public class Company extends RepresentationModel<Company> {
25+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
26+
private UUID id;
27+
@NotNull
28+
private String name;
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package test.org.springdoc.api.app5.entities;
2+
3+
import java.util.UUID;
4+
5+
import javax.validation.constraints.NotNull;
6+
7+
import com.fasterxml.jackson.annotation.JsonProperty;
8+
import lombok.AllArgsConstructor;
9+
import lombok.Builder;
10+
import lombok.Data;
11+
import lombok.EqualsAndHashCode;
12+
13+
import org.springframework.hateoas.RepresentationModel;
14+
import org.springframework.hateoas.server.core.Relation;
15+
16+
/**
17+
* @author bnasslahsen
18+
*/
19+
@AllArgsConstructor
20+
@Data
21+
@Builder
22+
@EqualsAndHashCode(callSuper = false)
23+
@Relation(collectionRelation = "companies", itemRelation = "company")
24+
public class CompanyDto extends RepresentationModel<CompanyDto> {
25+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
26+
private UUID id;
27+
@NotNull
28+
private String name;
29+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package test.org.springdoc.api.app5.hateoas;
2+
3+
import test.org.springdoc.api.app5.controller.CompanyController;
4+
import test.org.springdoc.api.app5.entities.Company;
5+
import test.org.springdoc.api.app5.entities.CompanyDto;
6+
7+
import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport;
8+
import org.springframework.lang.NonNull;
9+
import org.springframework.stereotype.Component;
10+
11+
/**
12+
* @author bnasslahsen
13+
*/
14+
@Component
15+
public class CompanyModelAssembler extends RepresentationModelAssemblerSupport<Company, CompanyDto> {
16+
17+
public CompanyModelAssembler() {
18+
super(CompanyController.class, CompanyDto.class);
19+
}
20+
21+
@Override
22+
@NonNull
23+
public CompanyDto toModel(@NonNull final Company company) {
24+
final CompanyDto dto = CompanyDto.builder()
25+
.id(company.getId())
26+
.name(company.getName())
27+
.build();
28+
return dto;
29+
}
30+
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
{
2+
"openapi": "3.0.1",
3+
"info": {
4+
"title": "OpenAPI definition",
5+
"version": "v0"
6+
},
7+
"servers": [
8+
{
9+
"url": "http://localhost",
10+
"description": "Generated server url"
11+
}
12+
],
13+
"paths": {
14+
"/": {
15+
"post": {
16+
"tags": [
17+
"company-controller"
18+
],
19+
"operationId": "create",
20+
"requestBody": {
21+
"description": "Details of the Item to be created",
22+
"content": {
23+
"application/json": {
24+
"schema": {
25+
"$ref": "#/components/schemas/CompanyDtoNew"
26+
}
27+
}
28+
},
29+
"required": true
30+
},
31+
"responses": {
32+
"200": {
33+
"description": "OK",
34+
"content": {
35+
"*/*": {
36+
"schema": {
37+
"$ref": "#/components/schemas/CompanyDto"
38+
}
39+
}
40+
}
41+
}
42+
}
43+
}
44+
}
45+
},
46+
"components": {
47+
"schemas": {
48+
"CompanyDtoNew": {
49+
"properties": {
50+
"id": {
51+
"type": "string",
52+
"format": "uuid"
53+
},
54+
"name": {
55+
"type": "string"
56+
}
57+
}
58+
},
59+
"CompanyDto": {
60+
"required": [
61+
"name"
62+
],
63+
"type": "object",
64+
"properties": {
65+
"id": {
66+
"type": "string",
67+
"format": "uuid",
68+
"readOnly": true
69+
},
70+
"name": {
71+
"type": "string"
72+
},
73+
"_links": {
74+
"$ref": "#/components/schemas/Links"
75+
}
76+
}
77+
},
78+
"Links": {
79+
"type": "object",
80+
"additionalProperties": {
81+
"$ref": "#/components/schemas/Link"
82+
}
83+
},
84+
"Link": {
85+
"type": "object",
86+
"properties": {
87+
"href": {
88+
"type": "string"
89+
},
90+
"hreflang": {
91+
"type": "string"
92+
},
93+
"title": {
94+
"type": "string"
95+
},
96+
"type": {
97+
"type": "string"
98+
},
99+
"deprecation": {
100+
"type": "string"
101+
},
102+
"profile": {
103+
"type": "string"
104+
},
105+
"name": {
106+
"type": "string"
107+
},
108+
"templated": {
109+
"type": "boolean"
110+
}
111+
}
112+
}
113+
}
114+
}
115+
}

springdoc-openapi-webmvc-core/src/test/resources/results/app90.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"content": {
6363
"application/json": {
6464
"schema": {
65-
"type": "string"
65+
"$ref": "#/components/schemas/User"
6666
},
6767
"examples": {
6868
"An example request with the minimum required fields to create.": {

0 commit comments

Comments
 (0)