Skip to content

Commit e2e45e6

Browse files
committed
Divide operations by request and response content types
Fixed #17877
1 parent b9460eb commit e2e45e6

4 files changed

Lines changed: 190 additions & 35 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java

Lines changed: 103 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,35 +1019,35 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
10191019
for (Map.Entry<String, PathItem> entry : openAPI.getPaths().entrySet()) {
10201020
String pathStr = entry.getKey();
10211021
PathItem path = entry.getValue();
1022-
List<Operation> getOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.GET, path.getGet());
1022+
List<Operation> getOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.GET, path.getGet());
10231023
if (!getOps.isEmpty()) {
10241024
path.addExtension("x-get", getOps);
10251025
}
1026-
List<Operation> putOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.PUT, path.getPut());
1026+
List<Operation> putOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.PUT, path.getPut());
10271027
if (!putOps.isEmpty()) {
10281028
path.addExtension("x-put", putOps);
10291029
}
1030-
List<Operation> postOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.POST, path.getPost());
1030+
List<Operation> postOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.POST, path.getPost());
10311031
if (!postOps.isEmpty()) {
10321032
path.addExtension("x-post", postOps);
10331033
}
1034-
List<Operation> deleteOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.DELETE, path.getDelete());
1034+
List<Operation> deleteOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.DELETE, path.getDelete());
10351035
if (!deleteOps.isEmpty()) {
10361036
path.addExtension("x-delete", deleteOps);
10371037
}
1038-
List<Operation> optionsOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.OPTIONS, path.getOptions());
1038+
List<Operation> optionsOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.OPTIONS, path.getOptions());
10391039
if (!optionsOps.isEmpty()) {
10401040
path.addExtension("x-options", optionsOps);
10411041
}
1042-
List<Operation> headOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.HEAD, path.getHead());
1042+
List<Operation> headOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.HEAD, path.getHead());
10431043
if (!headOps.isEmpty()) {
10441044
path.addExtension("x-head", headOps);
10451045
}
1046-
List<Operation> patchOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.PATCH, path.getPatch());
1046+
List<Operation> patchOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.PATCH, path.getPatch());
10471047
if (!patchOps.isEmpty()) {
10481048
path.addExtension("x-patch", patchOps);
10491049
}
1050-
List<Operation> traceOps = divideOperationsByContentType(pathStr, PathItem.HttpMethod.TRACE, path.getTrace());
1050+
List<Operation> traceOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.TRACE, path.getTrace());
10511051
if (!traceOps.isEmpty()) {
10521052
path.addExtension("x-trace", traceOps);
10531053
}
@@ -1135,24 +1135,29 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
11351135
}
11361136
}
11371137

1138-
private List<Operation> divideOperationsByContentType(String path, PathItem.HttpMethod httpMethod, Operation op) {
1138+
private List<Operation> divideOperationsByContentType(OpenAPI openAPI, String path, PathItem.HttpMethod httpMethod, Operation op) {
11391139

11401140
if (op == null) {
11411141
return Collections.emptyList();
11421142
}
11431143

1144+
var operationIndexes = new HashMap<String, Integer>();
1145+
operationIndexes.put(getOrGenerateOperationId(op, path, httpMethod.name()), 0);
1146+
11441147
var additionalOps = new ArrayList<Operation>();
1145-
divideOperationByRequestBody(path, httpMethod, op, additionalOps);
1148+
divideOperationByRequestBody(openAPI, path, httpMethod, op, additionalOps, operationIndexes);
11461149

11471150
// Check responses content types and divide operations by them
11481151

11491152
var responses = op.getResponses();
11501153
if (responses == null || responses.isEmpty()) {
11511154
return additionalOps;
11521155
}
1156+
var unwrappedResponses = new ApiResponses();
11531157
var allPossibleContentTypes = new ArrayList<String>();
11541158
for (var responseEntry : responses.entrySet()) {
1155-
var apiResponse = responseEntry.getValue();
1159+
var apiResponse = ModelUtils.getReferencedApiResponse(openAPI, responseEntry.getValue());
1160+
unwrappedResponses.put(responseEntry.getKey(), apiResponse);
11561161
if (apiResponse.getContent() == null) {
11571162
continue;
11581163
}
@@ -1166,6 +1171,8 @@ private List<Operation> divideOperationsByContentType(String path, PathItem.Http
11661171
if (allPossibleContentTypes.isEmpty() || allPossibleContentTypes.size() == 1) {
11671172
return additionalOps;
11681173
}
1174+
op.setResponses(unwrappedResponses);
1175+
responses = unwrappedResponses;
11691176

11701177
var apiResponsesByContentType = new HashMap<String, ApiResponses>();
11711178
for (var contentType : allPossibleContentTypes) {
@@ -1184,7 +1191,7 @@ private List<Operation> divideOperationsByContentType(String path, PathItem.Http
11841191
.description(response.getDescription())
11851192
.headers(response.getHeaders())
11861193
.links(response.getLinks())
1187-
.extensions(response.getExtensions())
1194+
.extensions(response.getExtensions() != null ? new LinkedHashMap<>(response.getExtensions()) : new LinkedHashMap<>())
11881195
.$ref(response.get$ref())
11891196
.content(new Content()
11901197
.addMediaType(contentType, mediaType)
@@ -1194,21 +1201,34 @@ private List<Operation> divideOperationsByContentType(String path, PathItem.Http
11941201
apiResponsesByContentType.put(contentType, apiResponses);
11951202
}
11961203

1204+
var addedContentTypes = new ArrayList<String>();
11971205
var finalAdditionalOps = new ArrayList<Operation>();
1198-
divideOperationByResponses(path, httpMethod, op, apiResponsesByContentType, finalAdditionalOps);
1206+
divideOperationByResponses(path, httpMethod, op, apiResponsesByContentType, finalAdditionalOps, addedContentTypes, operationIndexes);
11991207
for (var additionalOp : additionalOps) {
12001208
finalAdditionalOps.add(additionalOp);
1201-
divideOperationByResponses(path, httpMethod, additionalOp, apiResponsesByContentType, finalAdditionalOps);
1209+
divideOperationByResponses(path, httpMethod, additionalOp, apiResponsesByContentType, finalAdditionalOps, addedContentTypes, operationIndexes);
1210+
}
1211+
1212+
// remove correct processed contentTypes
1213+
apiResponsesByContentType.entrySet().removeIf(stringMediaTypeEntry -> addedContentTypes.contains(stringMediaTypeEntry.getKey()));
1214+
1215+
if (!apiResponsesByContentType.isEmpty()) {
1216+
appendCommonResponseMediaTypes(op, apiResponsesByContentType.values());
1217+
for (var additionalOp : additionalOps) {
1218+
appendCommonResponseMediaTypes(additionalOp, apiResponsesByContentType.values());
1219+
}
12021220
}
12031221

12041222
return finalAdditionalOps;
12051223
}
12061224

1207-
private void divideOperationByRequestBody(String path, PathItem.HttpMethod httpMethod, Operation op, List<Operation> additionalOps) {
1208-
RequestBody body = op.getRequestBody();
1225+
private void divideOperationByRequestBody(OpenAPI openAPI, String path, PathItem.HttpMethod httpMethod, Operation op,
1226+
List<Operation> additionalOps, Map<String, Integer> operationIndexes) {
1227+
RequestBody body = ModelUtils.getReferencedRequestBody(openAPI, op.getRequestBody());
12091228
if (body == null || body.getContent() == null) {
12101229
return;
12111230
}
1231+
op.setRequestBody(body);
12121232
Content content = body.getContent();
12131233
if (content.size() <= 1) {
12141234
return;
@@ -1224,28 +1244,34 @@ private void divideOperationByRequestBody(String path, PathItem.HttpMethod httpM
12241244
var foundSameOpSignature = false;
12251245
// group by response content type
12261246
if (groupByResponseContentType) {
1227-
for (var additionalOp : additionalOps) {
1228-
RequestBody additionalBody = additionalOp.getRequestBody();
1229-
if (additionalBody == null || additionalBody.getContent() == null) {
1230-
return;
1247+
if (firstEntry.getValue().equals(mediaType)) {
1248+
if (!groupByRequestAndResponseContentType) {
1249+
foundSameOpSignature = true;
12311250
}
1232-
for (var addContentEntry : additionalBody.getContent().entrySet()) {
1233-
if (addContentEntry.getValue().equals(mediaType)) {
1234-
foundSameOpSignature = true;
1251+
} else {
1252+
for (var additionalOp : additionalOps) {
1253+
RequestBody additionalBody = ModelUtils.getReferencedRequestBody(openAPI, additionalOp.getRequestBody());
1254+
if (additionalBody == null || additionalBody.getContent() == null) {
1255+
return;
1256+
}
1257+
for (var addContentEntry : additionalBody.getContent().entrySet()) {
1258+
if (addContentEntry.getValue().equals(mediaType)) {
1259+
foundSameOpSignature = true;
1260+
break;
1261+
}
1262+
}
1263+
if (foundSameOpSignature) {
1264+
additionalBody.getContent().put(contentType, mediaType);
12351265
break;
12361266
}
12371267
}
1238-
if (foundSameOpSignature) {
1239-
additionalBody.getContent().put(contentType, mediaType);
1240-
break;
1241-
}
12421268
}
12431269
}
12441270

1245-
mediaTypesToRemove.add(contentType);
12461271
if (groupByResponseContentType && foundSameOpSignature) {
12471272
continue;
12481273
}
1274+
mediaTypesToRemove.add(contentType);
12491275

12501276
var apiResponsesCopy = new ApiResponses();
12511277
apiResponsesCopy.putAll(op.getResponses());
@@ -1254,9 +1280,9 @@ private void divideOperationByRequestBody(String path, PathItem.HttpMethod httpM
12541280
.deprecated(op.getDeprecated())
12551281
.callbacks(op.getCallbacks())
12561282
.description(op.getDescription())
1257-
.extensions(op.getExtensions())
1283+
.extensions(op.getExtensions() != null ? new LinkedHashMap<>(op.getExtensions()) : new LinkedHashMap<>())
12581284
.externalDocs(op.getExternalDocs())
1259-
.operationId(getOrGenerateOperationId(op, path, httpMethod.name()))
1285+
.operationId(calcOperationId(path, httpMethod, op, operationIndexes))
12601286
.parameters(op.getParameters())
12611287
.responses(apiResponsesCopy)
12621288
.security(op.getSecurity())
@@ -1276,13 +1302,33 @@ private void divideOperationByRequestBody(String path, PathItem.HttpMethod httpM
12761302
}
12771303
}
12781304

1305+
private String calcOperationId(String path, PathItem.HttpMethod httpMethod, Operation op, Map<String, Integer> operationIndexes) {
1306+
var operationId = getOrGenerateOperationId(op, path, httpMethod.name());
1307+
var index = operationIndexes.get(operationId);
1308+
if (index != null) {
1309+
index++;
1310+
operationId += "_" + index;
1311+
operationIndexes.put(operationId, index);
1312+
} else {
1313+
operationIndexes.put(operationId, 0);
1314+
}
1315+
return operationId;
1316+
}
1317+
12791318
private void divideOperationByResponses(
12801319
String path,
12811320
PathItem.HttpMethod httpMethod,
12821321
Operation op,
12831322
Map<String, ApiResponses> apiResponsesByContentType,
1284-
List<Operation> additionalOps
1323+
List<Operation> additionalOps,
1324+
List<String> addedContentTypes,
1325+
Map<String, Integer> operationIndexes
12851326
) {
1327+
1328+
if (!groupByResponseContentType) {
1329+
return;
1330+
}
1331+
12861332
var isFirst = true;
12871333
for (var entry : apiResponsesByContentType.entrySet()) {
12881334
var contentType = entry.getKey();
@@ -1295,6 +1341,7 @@ private void divideOperationByResponses(
12951341
&& !requestBody.getContent().containsKey(contentType)) {
12961342
continue;
12971343
}
1344+
addedContentTypes.add(contentType);
12981345
if (isFirst) {
12991346
op.setResponses(apiResponses);
13001347
isFirst = false;
@@ -1307,7 +1354,7 @@ private void divideOperationByResponses(
13071354
.description(op.getDescription())
13081355
.extensions(op.getExtensions())
13091356
.externalDocs(op.getExternalDocs())
1310-
.operationId(getOrGenerateOperationId(op, path, httpMethod.name()))
1357+
.operationId(calcOperationId(path, httpMethod, op, operationIndexes))
13111358
.parameters(op.getParameters())
13121359
.responses(apiResponses)
13131360
.security(op.getSecurity())
@@ -1319,6 +1366,30 @@ private void divideOperationByResponses(
13191366
}
13201367
}
13211368

1369+
private void appendCommonResponseMediaTypes(Operation op, Collection<ApiResponses> commonResponsesList) {
1370+
if (commonResponsesList.isEmpty()) {
1371+
return;
1372+
}
1373+
1374+
for (var commonResponses : commonResponsesList) {
1375+
var adOpResponses = op.getResponses();
1376+
for (var responseEntry : adOpResponses.entrySet()) {
1377+
var content = responseEntry.getValue().getContent();
1378+
var commonResponse = commonResponses.get(responseEntry.getKey());
1379+
if (commonResponse != null && commonResponse.getContent() != null) {
1380+
if (content == null) {
1381+
content = new Content();
1382+
}
1383+
for (var commonResponseEntry : commonResponse.getContent().entrySet()) {
1384+
if (!content.containsKey(commonResponseEntry.getKey())) {
1385+
content.addMediaType(commonResponseEntry.getKey(), commonResponseEntry.getValue());
1386+
}
1387+
}
1388+
}
1389+
}
1390+
}
1391+
}
1392+
13221393
// override with any special handling of the entire OpenAPI spec document
13231394
@Override
13241395
@SuppressWarnings("unused")

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/SpringCodegen.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
10021002

10031003
// add Pageable import only if x-spring-paginated explicitly used
10041004
// this allows to use a custom Pageable schema without importing Spring Pageable.
1005-
if (Boolean.TRUE.equals(operation.getExtensions().get("x-spring-paginated"))) {
1005+
if (operation.getExtensions() != null && Boolean.TRUE.equals(operation.getExtensions().get("x-spring-paginated"))) {
10061006
importMapping.put("Pageable", "org.springframework.data.domain.Pageable");
10071007
}
10081008

@@ -1082,7 +1082,7 @@ public CodegenOperation fromOperation(String path, String httpMethod, Operation
10821082

10831083
private Set<String> reformatProvideArgsParams(Operation operation) {
10841084
Set<String> provideArgsClassSet = new HashSet<>();
1085-
Object argObj = operation.getExtensions().get("x-spring-provide-args");
1085+
Object argObj = operation.getExtensions() != null ? operation.getExtensions().get("x-spring-provide-args") : null;
10861086
if (argObj instanceof List) {
10871087
List<String> provideArgs = (List<String>) argObj;
10881088
if (!provideArgs.isEmpty()) {
@@ -1111,7 +1111,7 @@ private Set<String> reformatProvideArgsParams(Operation operation) {
11111111
formattedArgs.add(newArg);
11121112
}
11131113
}
1114-
operation.getExtensions().put("x-spring-provide-args", formattedArgs);
1114+
operation.addExtension("x-spring-provide-args", formattedArgs);
11151115
}
11161116
}
11171117
return provideArgsClassSet;

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/micronaut/JavaMicronautClientCodegenTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -759,4 +759,17 @@ public void testMultipleContentTypesToPathFalseFalse() {
759759
" );"
760760
);
761761
}
762+
763+
@Test
764+
public void testMultipleContentTypesWithRefs() {
765+
766+
var codegen = new JavaMicronautClientCodegen();
767+
codegen.setGroupByRequestAndResponseContentType(true);
768+
codegen.setGroupByResponseContentType(true);
769+
String outputPath = generateFiles(codegen, "src/test/resources/3_0/java/multiple-content-types-2.yaml", CodegenConstants.APIS, CodegenConstants.MODELS);
770+
771+
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/DefaultApi.java",
772+
""
773+
);
774+
}
762775
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
openapi: 3.0.3
2+
info:
3+
version: "1"
4+
title: Multiple Content Types for same request
5+
paths:
6+
/pet:
7+
post:
8+
tags:
9+
- pet
10+
summary: Add a new pet to the store
11+
description: ''
12+
operationId: addPet
13+
responses:
14+
'200':
15+
$ref: "#/components/responses/200"
16+
'405':
17+
description: Invalid input
18+
security:
19+
- petstore_auth:
20+
- 'write:pets'
21+
- 'read:pets'
22+
requestBody:
23+
$ref: '#/components/requestBodies/Pet'
24+
components:
25+
responses:
26+
'200':
27+
description: successful operation
28+
content:
29+
application/xml:
30+
schema:
31+
$ref: '#/components/schemas/Pet'
32+
application/json:
33+
schema:
34+
$ref: '#/components/schemas/Pet'
35+
application/yaml:
36+
schema:
37+
$ref: '#/components/schemas/MySchema'
38+
requestBodies:
39+
Pet:
40+
content:
41+
application/json:
42+
schema:
43+
$ref: '#/components/schemas/Pet'
44+
application/xml:
45+
schema:
46+
$ref: '#/components/schemas/Pet'
47+
text/json:
48+
schema:
49+
$ref: '#/components/schemas/MySchema'
50+
description: Pet object that needs to be added to the store
51+
required: true
52+
schemas:
53+
MySchema:
54+
type: object
55+
properties:
56+
id:
57+
type: string
58+
Pet:
59+
title: a Pet
60+
description: A pet for sale in the pet store
61+
type: object
62+
required:
63+
- name
64+
- photoUrls
65+
properties:
66+
id:
67+
type: integer
68+
format: int64
69+
name:
70+
type: string
71+
example: doggie

0 commit comments

Comments
 (0)