86
86
import java .util .stream .Collectors ;
87
87
import java .util .stream .Stream ;
88
88
89
+ import static org .openapitools .codegen .CodegenConstants .DIVIDE_OPERATIONS_BY_CONTENT_TYPE ;
89
90
import static org .openapitools .codegen .CodegenConstants .UNSUPPORTED_V310_SPEC_MSG ;
90
91
import static org .openapitools .codegen .utils .CamelizeOption .LOWERCASE_FIRST_LETTER ;
91
92
import static org .openapitools .codegen .utils .OnceLogger .once ;
@@ -332,6 +333,10 @@ apiTemplateFiles are for API outputs only (controllers/handlers).
332
333
333
334
// Whether to automatically hardcode params that are considered Constants by OpenAPI Spec
334
335
@ Setter protected boolean autosetConstants = false ;
336
+ @ Setter
337
+ protected boolean groupByRequestAndResponseContentType = true ;
338
+ @ Setter
339
+ protected boolean groupByResponseContentType = true ;
335
340
336
341
@ Override
337
342
public boolean getAddSuffixToDuplicateOperationNicknames () {
@@ -392,8 +397,9 @@ public void processOpts() {
392
397
convertPropertyToBooleanAndWriteBack (CodegenConstants .DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT , this ::setDisallowAdditionalPropertiesIfNotPresent );
393
398
convertPropertyToBooleanAndWriteBack (CodegenConstants .ENUM_UNKNOWN_DEFAULT_CASE , this ::setEnumUnknownDefaultCase );
394
399
convertPropertyToBooleanAndWriteBack (CodegenConstants .AUTOSET_CONSTANTS , this ::setAutosetConstants );
395
- }
396
-
400
+ convertPropertyToBooleanAndWriteBack (CodegenConstants .GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE , this ::setGroupByRequestAndResponseContentType );
401
+ convertPropertyToBooleanAndWriteBack (CodegenConstants .GROUP_BY_RESPONSE_CONTENT_TYPE , this ::setGroupByResponseContentType );
402
+ }
397
403
398
404
/***
399
405
* Preset map builder with commonly used Mustache lambdas.
@@ -898,7 +904,7 @@ public String toEnumValue(String value, String datatype) {
898
904
* @return the sanitized variable name for enum
899
905
*/
900
906
public String toEnumVarName (String value , String datatype ) {
901
- if (value .length () == 0 ) {
907
+ if (value .isEmpty () ) {
902
908
return "EMPTY" ;
903
909
}
904
910
@@ -999,6 +1005,49 @@ public void postProcessParameter(CodegenParameter parameter) {
999
1005
@ Override
1000
1006
@ SuppressWarnings ("unused" )
1001
1007
public void preprocessOpenAPI (OpenAPI openAPI ) {
1008
+
1009
+ var divideOperationsByContentType = Boolean .parseBoolean (GlobalSettings .getProperty (DIVIDE_OPERATIONS_BY_CONTENT_TYPE , "false" ));
1010
+
1011
+ if (divideOperationsByContentType && openAPI .getPaths () != null && !openAPI .getPaths ().isEmpty ()) {
1012
+
1013
+ for (Map .Entry <String , PathItem > entry : openAPI .getPaths ().entrySet ()) {
1014
+ String pathStr = entry .getKey ();
1015
+ PathItem path = entry .getValue ();
1016
+ List <Operation > getOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .GET , path .getGet ());
1017
+ if (!getOps .isEmpty ()) {
1018
+ path .addExtension ("x-get" , getOps );
1019
+ }
1020
+ List <Operation > putOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .PUT , path .getPut ());
1021
+ if (!putOps .isEmpty ()) {
1022
+ path .addExtension ("x-put" , putOps );
1023
+ }
1024
+ List <Operation > postOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .POST , path .getPost ());
1025
+ if (!postOps .isEmpty ()) {
1026
+ path .addExtension ("x-post" , postOps );
1027
+ }
1028
+ List <Operation > deleteOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .DELETE , path .getDelete ());
1029
+ if (!deleteOps .isEmpty ()) {
1030
+ path .addExtension ("x-delete" , deleteOps );
1031
+ }
1032
+ List <Operation > optionsOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .OPTIONS , path .getOptions ());
1033
+ if (!optionsOps .isEmpty ()) {
1034
+ path .addExtension ("x-options" , optionsOps );
1035
+ }
1036
+ List <Operation > headOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .HEAD , path .getHead ());
1037
+ if (!headOps .isEmpty ()) {
1038
+ path .addExtension ("x-head" , headOps );
1039
+ }
1040
+ List <Operation > patchOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .PATCH , path .getPatch ());
1041
+ if (!patchOps .isEmpty ()) {
1042
+ path .addExtension ("x-patch" , patchOps );
1043
+ }
1044
+ List <Operation > traceOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .TRACE , path .getTrace ());
1045
+ if (!traceOps .isEmpty ()) {
1046
+ path .addExtension ("x-trace" , traceOps );
1047
+ }
1048
+ }
1049
+ }
1050
+
1002
1051
if (useOneOfInterfaces && openAPI .getComponents () != null ) {
1003
1052
// we process the openapi schema here to find oneOf schemas and create interface models for them
1004
1053
Map <String , Schema > schemas = new HashMap <>(openAPI .getComponents ().getSchemas ());
@@ -1080,6 +1129,184 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
1080
1129
}
1081
1130
}
1082
1131
1132
+ private List <Operation > divideOperationsByContentType (String path , PathItem .HttpMethod httpMethod , Operation op ) {
1133
+
1134
+ if (op == null ) {
1135
+ return Collections .emptyList ();
1136
+ }
1137
+
1138
+ var additionalOps = new ArrayList <Operation >();
1139
+ divideOperationByRequestBody (path , httpMethod , op , additionalOps );
1140
+
1141
+ // Check responses content types and divide operations by them
1142
+
1143
+ var responses = op .getResponses ();
1144
+ if (responses == null || responses .isEmpty ()) {
1145
+ return additionalOps ;
1146
+ }
1147
+ var allPossibleContentTypes = new ArrayList <String >();
1148
+ for (var responseEntry : responses .entrySet ()) {
1149
+ for (var contentType : responseEntry .getValue ().getContent ().keySet ()) {
1150
+ contentType = contentType .toLowerCase ();
1151
+ if (!allPossibleContentTypes .contains (contentType )) {
1152
+ allPossibleContentTypes .add (contentType );
1153
+ }
1154
+ }
1155
+ }
1156
+ if (allPossibleContentTypes .isEmpty () || allPossibleContentTypes .size () == 1 ) {
1157
+ return additionalOps ;
1158
+ }
1159
+
1160
+ var apiResponsesByContentType = new HashMap <String , ApiResponses >();
1161
+ for (var contentType : allPossibleContentTypes ) {
1162
+ var apiResponses = new ApiResponses ();
1163
+ for (var responseEntry : responses .entrySet ()) {
1164
+ var code = responseEntry .getKey ();
1165
+ var response = responseEntry .getValue ();
1166
+ var mediaType = response .getContent ().get (contentType );
1167
+ if (mediaType == null ) {
1168
+ continue ;
1169
+ }
1170
+ apiResponses .addApiResponse (code , new ApiResponse ()
1171
+ .description (response .getDescription ())
1172
+ .headers (response .getHeaders ())
1173
+ .links (response .getLinks ())
1174
+ .extensions (response .getExtensions ())
1175
+ .$ref (response .get$ref ())
1176
+ .content (new Content ()
1177
+ .addMediaType (contentType , mediaType )
1178
+ )
1179
+ );
1180
+ }
1181
+ apiResponsesByContentType .put (contentType , apiResponses );
1182
+ }
1183
+
1184
+ var finalAdditionalOps = new ArrayList <Operation >();
1185
+ divideOperationByResponses (path , httpMethod , op , apiResponsesByContentType , finalAdditionalOps );
1186
+ for (var additionalOp : additionalOps ) {
1187
+ finalAdditionalOps .add (additionalOp );
1188
+ divideOperationByResponses (path , httpMethod , additionalOp , apiResponsesByContentType , finalAdditionalOps );
1189
+ }
1190
+
1191
+ return finalAdditionalOps ;
1192
+ }
1193
+
1194
+ private void divideOperationByRequestBody (String path , PathItem .HttpMethod httpMethod , Operation op , List <Operation > additionalOps ) {
1195
+ RequestBody body = op .getRequestBody ();
1196
+ if (body == null || body .getContent () == null ) {
1197
+ return ;
1198
+ }
1199
+ Content content = body .getContent ();
1200
+ if (content .size () <= 1 ) {
1201
+ return ;
1202
+ }
1203
+ var firstEntry = content .entrySet ().iterator ().next ();
1204
+ var mediaTypesToRemove = new ArrayList <String >();
1205
+ var withoutContent = false ;
1206
+ for (var entry : content .entrySet ()) {
1207
+ if (mediaTypesToRemove .contains (entry .getKey ()) || entry .getKey ().equals (firstEntry .getKey ())) {
1208
+ continue ;
1209
+ }
1210
+ var foundSameOpSignature = false ;
1211
+ // group by response content type
1212
+ if (groupByResponseContentType ) {
1213
+ for (var additionalOp : additionalOps ) {
1214
+ RequestBody additionalBody = additionalOp .getRequestBody ();
1215
+ if (additionalBody == null || additionalBody .getContent () == null ) {
1216
+ return ;
1217
+ }
1218
+ for (var addContentEntry : additionalBody .getContent ().entrySet ()) {
1219
+ if (addContentEntry .getValue ().equals (entry .getValue ())) {
1220
+ foundSameOpSignature = true ;
1221
+ break ;
1222
+ }
1223
+ }
1224
+ if (foundSameOpSignature ) {
1225
+ additionalBody .getContent ().put (entry .getKey (), entry .getValue ());
1226
+ break ;
1227
+ }
1228
+ }
1229
+ }
1230
+ if (withoutContent ) {
1231
+ break ;
1232
+ }
1233
+ mediaTypesToRemove .add (entry .getKey ());
1234
+ if (groupByResponseContentType && foundSameOpSignature ) {
1235
+ continue ;
1236
+ }
1237
+
1238
+ var apiResponsesCopy = new ApiResponses ();
1239
+ apiResponsesCopy .putAll (op .getResponses ());
1240
+
1241
+ additionalOps .add (new Operation ()
1242
+ .deprecated (op .getDeprecated ())
1243
+ .callbacks (op .getCallbacks ())
1244
+ .description (op .getDescription ())
1245
+ .extensions (op .getExtensions ())
1246
+ .externalDocs (op .getExternalDocs ())
1247
+ .operationId (getOrGenerateOperationId (op , path , httpMethod .name ()))
1248
+ .parameters (op .getParameters ())
1249
+ .responses (apiResponsesCopy )
1250
+ .security (op .getSecurity ())
1251
+ .servers (op .getServers ())
1252
+ .summary (op .getSummary ())
1253
+ .tags (op .getTags ())
1254
+ .requestBody (new RequestBody ()
1255
+ .description (body .getDescription ())
1256
+ .extensions (body .getExtensions ())
1257
+ .content (new Content ()
1258
+ .addMediaType (entry .getKey (), entry .getValue ()))
1259
+ )
1260
+ );
1261
+ }
1262
+ if (!mediaTypesToRemove .isEmpty ()) {
1263
+ content .entrySet ().removeIf (stringMediaTypeEntry -> mediaTypesToRemove .contains (stringMediaTypeEntry .getKey ()));
1264
+ }
1265
+ }
1266
+
1267
+ private void divideOperationByResponses (
1268
+ String path ,
1269
+ PathItem .HttpMethod httpMethod ,
1270
+ Operation op ,
1271
+ Map <String , ApiResponses > apiResponsesByContentType ,
1272
+ List <Operation > additionalOps
1273
+ ) {
1274
+ var isFirst = true ;
1275
+ for (var entry : apiResponsesByContentType .entrySet ()) {
1276
+ var contentType = entry .getKey ();
1277
+ var apiResponses = entry .getValue ();
1278
+ var requestBody = op .getRequestBody ();
1279
+ // group by requestBody contentType
1280
+ if (groupByRequestAndResponseContentType
1281
+ && requestBody != null
1282
+ && requestBody .getContent () != null
1283
+ && !requestBody .getContent ().containsKey (contentType )) {
1284
+ continue ;
1285
+ }
1286
+ if (isFirst ) {
1287
+ op .setResponses (apiResponses );
1288
+ isFirst = false ;
1289
+ continue ;
1290
+ }
1291
+
1292
+ additionalOps .add (new Operation ()
1293
+ .deprecated (op .getDeprecated ())
1294
+ .callbacks (op .getCallbacks ())
1295
+ .description (op .getDescription ())
1296
+ .extensions (op .getExtensions ())
1297
+ .externalDocs (op .getExternalDocs ())
1298
+ .operationId (getOrGenerateOperationId (op , path , httpMethod .name ()))
1299
+ .parameters (op .getParameters ())
1300
+ .responses (apiResponses )
1301
+ .security (op .getSecurity ())
1302
+ .servers (op .getServers ())
1303
+ .summary (op .getSummary ())
1304
+ .tags (op .getTags ())
1305
+ .requestBody (requestBody )
1306
+ );
1307
+ }
1308
+ }
1309
+
1083
1310
// override with any special handling of the entire OpenAPI spec document
1084
1311
@ Override
1085
1312
@ SuppressWarnings ("unused" )
@@ -1164,8 +1391,7 @@ public String encodePath(String input) {
1164
1391
*/
1165
1392
@ Override
1166
1393
public String escapeUnsafeCharacters (String input ) {
1167
- LOGGER .warn ("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " +
1168
- "unsafe characters" );
1394
+ LOGGER .warn ("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape unsafe characters" );
1169
1395
// doing nothing by default and code generator should implement
1170
1396
// the logic to prevent code injection
1171
1397
// later we'll make this method abstract to make sure
@@ -1181,8 +1407,7 @@ public String escapeUnsafeCharacters(String input) {
1181
1407
*/
1182
1408
@ Override
1183
1409
public String escapeQuotationMark (String input ) {
1184
- LOGGER .warn ("escapeQuotationMark should be overridden in the code generator with proper logic to escape " +
1185
- "single/double quote" );
1410
+ LOGGER .warn ("escapeQuotationMark should be overridden in the code generator with proper logic to escape single/double quote" );
1186
1411
return input .replace ("\" " , "\\ \" " );
1187
1412
}
1188
1413
@@ -1755,6 +1980,10 @@ public DefaultCodegen() {
1755
1980
// option to change the order of form/body parameter
1756
1981
cliOptions .add (CliOption .newBoolean (CodegenConstants .PREPEND_FORM_OR_BODY_PARAMETERS ,
1757
1982
CodegenConstants .PREPEND_FORM_OR_BODY_PARAMETERS_DESC ).defaultValue (Boolean .FALSE .toString ()));
1983
+ cliOptions .add (CliOption .newBoolean (CodegenConstants .GROUP_BY_RESPONSE_CONTENT_TYPE ,
1984
+ CodegenConstants .GROUP_BY_RESPONSE_CONTENT_TYPE_DESC ).defaultValue (Boolean .TRUE .toString ()));
1985
+ cliOptions .add (CliOption .newBoolean (CodegenConstants .GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE ,
1986
+ CodegenConstants .GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE_DESC ).defaultValue (Boolean .TRUE .toString ()));
1758
1987
1759
1988
// option to change how we process + set the data in the discriminator mapping
1760
1989
CliOption legacyDiscriminatorBehaviorOpt = CliOption .newBoolean (CodegenConstants .LEGACY_DISCRIMINATOR_BEHAVIOR , CodegenConstants .LEGACY_DISCRIMINATOR_BEHAVIOR_DESC ).defaultValue (Boolean .TRUE .toString ());
0 commit comments