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