@@ -332,6 +332,10 @@ apiTemplateFiles are for API outputs only (controllers/handlers).
332332
333333 // Whether to automatically hardcode params that are considered Constants by OpenAPI Spec
334334 @ Setter protected boolean autosetConstants = false ;
335+ @ Setter
336+ protected boolean groupByRequestAndResponseContentType = true ;
337+ @ Setter
338+ protected boolean groupByResponseContentType = true ;
335339
336340 @ Override
337341 public boolean getAddSuffixToDuplicateOperationNicknames () {
@@ -391,9 +395,10 @@ public void processOpts() {
391395 convertPropertyToBooleanAndWriteBack (CodegenConstants .DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT , this ::setDisallowAdditionalPropertiesIfNotPresent );
392396 convertPropertyToBooleanAndWriteBack (CodegenConstants .ENUM_UNKNOWN_DEFAULT_CASE , this ::setEnumUnknownDefaultCase );
393397 convertPropertyToBooleanAndWriteBack (CodegenConstants .AUTOSET_CONSTANTS , this ::setAutosetConstants );
398+ convertPropertyToBooleanAndWriteBack (CodegenConstants .GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE , this ::setGroupByRequestAndResponseContentType );
399+ convertPropertyToBooleanAndWriteBack (CodegenConstants .GROUP_BY_RESPONSE_CONTENT_TYPE , this ::setGroupByResponseContentType );
394400 }
395401
396-
397402 /***
398403 * Preset map builder with commonly used Mustache lambdas.
399404 *
@@ -907,7 +912,7 @@ public String toEnumValue(String value, String datatype) {
907912 * @return the sanitized variable name for enum
908913 */
909914 public String toEnumVarName (String value , String datatype ) {
910- if (value .length () == 0 ) {
915+ if (value .isEmpty () ) {
911916 return "EMPTY" ;
912917 }
913918
@@ -1008,6 +1013,47 @@ public void postProcessParameter(CodegenParameter parameter) {
10081013 @ Override
10091014 @ SuppressWarnings ("unused" )
10101015 public void preprocessOpenAPI (OpenAPI openAPI ) {
1016+
1017+ if (supportsDividingOperationsByContentType () && openAPI .getPaths () != null && !openAPI .getPaths ().isEmpty ()) {
1018+
1019+ for (Map .Entry <String , PathItem > entry : openAPI .getPaths ().entrySet ()) {
1020+ String pathStr = entry .getKey ();
1021+ PathItem path = entry .getValue ();
1022+ List <Operation > getOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .GET , path .getGet ());
1023+ if (!getOps .isEmpty ()) {
1024+ path .addExtension ("x-get" , getOps );
1025+ }
1026+ List <Operation > putOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .PUT , path .getPut ());
1027+ if (!putOps .isEmpty ()) {
1028+ path .addExtension ("x-put" , putOps );
1029+ }
1030+ List <Operation > postOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .POST , path .getPost ());
1031+ if (!postOps .isEmpty ()) {
1032+ path .addExtension ("x-post" , postOps );
1033+ }
1034+ List <Operation > deleteOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .DELETE , path .getDelete ());
1035+ if (!deleteOps .isEmpty ()) {
1036+ path .addExtension ("x-delete" , deleteOps );
1037+ }
1038+ List <Operation > optionsOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .OPTIONS , path .getOptions ());
1039+ if (!optionsOps .isEmpty ()) {
1040+ path .addExtension ("x-options" , optionsOps );
1041+ }
1042+ List <Operation > headOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .HEAD , path .getHead ());
1043+ if (!headOps .isEmpty ()) {
1044+ path .addExtension ("x-head" , headOps );
1045+ }
1046+ List <Operation > patchOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .PATCH , path .getPatch ());
1047+ if (!patchOps .isEmpty ()) {
1048+ path .addExtension ("x-patch" , patchOps );
1049+ }
1050+ List <Operation > traceOps = divideOperationsByContentType (pathStr , PathItem .HttpMethod .TRACE , path .getTrace ());
1051+ if (!traceOps .isEmpty ()) {
1052+ path .addExtension ("x-trace" , traceOps );
1053+ }
1054+ }
1055+ }
1056+
10111057 if (useOneOfInterfaces && openAPI .getComponents () != null ) {
10121058 // we process the openapi schema here to find oneOf schemas and create interface models for them
10131059 Map <String , Schema > schemas = new HashMap <>(openAPI .getComponents ().getSchemas ());
@@ -1089,6 +1135,190 @@ public void preprocessOpenAPI(OpenAPI openAPI) {
10891135 }
10901136 }
10911137
1138+ private List <Operation > divideOperationsByContentType (String path , PathItem .HttpMethod httpMethod , Operation op ) {
1139+
1140+ if (op == null ) {
1141+ return Collections .emptyList ();
1142+ }
1143+
1144+ var additionalOps = new ArrayList <Operation >();
1145+ divideOperationByRequestBody (path , httpMethod , op , additionalOps );
1146+
1147+ // Check responses content types and divide operations by them
1148+
1149+ var responses = op .getResponses ();
1150+ if (responses == null || responses .isEmpty ()) {
1151+ return additionalOps ;
1152+ }
1153+ var allPossibleContentTypes = new ArrayList <String >();
1154+ for (var responseEntry : responses .entrySet ()) {
1155+ var apiResponse = responseEntry .getValue ();
1156+ if (apiResponse .getContent () == null ) {
1157+ continue ;
1158+ }
1159+ for (var contentType : apiResponse .getContent ().keySet ()) {
1160+ contentType = contentType .toLowerCase ();
1161+ if (!allPossibleContentTypes .contains (contentType )) {
1162+ allPossibleContentTypes .add (contentType );
1163+ }
1164+ }
1165+ }
1166+ if (allPossibleContentTypes .isEmpty () || allPossibleContentTypes .size () == 1 ) {
1167+ return additionalOps ;
1168+ }
1169+
1170+ var apiResponsesByContentType = new HashMap <String , ApiResponses >();
1171+ for (var contentType : allPossibleContentTypes ) {
1172+ var apiResponses = new ApiResponses ();
1173+ for (var responseEntry : responses .entrySet ()) {
1174+ var code = responseEntry .getKey ();
1175+ var response = responseEntry .getValue ();
1176+ if (response .getContent () == null ) {
1177+ continue ;
1178+ }
1179+ var mediaType = response .getContent ().get (contentType );
1180+ if (mediaType == null ) {
1181+ continue ;
1182+ }
1183+ apiResponses .addApiResponse (code , new ApiResponse ()
1184+ .description (response .getDescription ())
1185+ .headers (response .getHeaders ())
1186+ .links (response .getLinks ())
1187+ .extensions (response .getExtensions ())
1188+ .$ref (response .get$ref ())
1189+ .content (new Content ()
1190+ .addMediaType (contentType , mediaType )
1191+ )
1192+ );
1193+ }
1194+ apiResponsesByContentType .put (contentType , apiResponses );
1195+ }
1196+
1197+ var finalAdditionalOps = new ArrayList <Operation >();
1198+ divideOperationByResponses (path , httpMethod , op , apiResponsesByContentType , finalAdditionalOps );
1199+ for (var additionalOp : additionalOps ) {
1200+ finalAdditionalOps .add (additionalOp );
1201+ divideOperationByResponses (path , httpMethod , additionalOp , apiResponsesByContentType , finalAdditionalOps );
1202+ }
1203+
1204+ return finalAdditionalOps ;
1205+ }
1206+
1207+ private void divideOperationByRequestBody (String path , PathItem .HttpMethod httpMethod , Operation op , List <Operation > additionalOps ) {
1208+ RequestBody body = op .getRequestBody ();
1209+ if (body == null || body .getContent () == null ) {
1210+ return ;
1211+ }
1212+ Content content = body .getContent ();
1213+ if (content .size () <= 1 ) {
1214+ return ;
1215+ }
1216+ var firstEntry = content .entrySet ().iterator ().next ();
1217+ var mediaTypesToRemove = new ArrayList <String >();
1218+ for (var entry : content .entrySet ()) {
1219+ var contentType = entry .getKey ();
1220+ MediaType mediaType = entry .getValue ();
1221+ if (mediaTypesToRemove .contains (contentType ) || contentType .equals (firstEntry .getKey ())) {
1222+ continue ;
1223+ }
1224+ var foundSameOpSignature = false ;
1225+ // group by response content type
1226+ if (groupByResponseContentType ) {
1227+ for (var additionalOp : additionalOps ) {
1228+ RequestBody additionalBody = additionalOp .getRequestBody ();
1229+ if (additionalBody == null || additionalBody .getContent () == null ) {
1230+ return ;
1231+ }
1232+ for (var addContentEntry : additionalBody .getContent ().entrySet ()) {
1233+ if (addContentEntry .getValue ().equals (mediaType )) {
1234+ foundSameOpSignature = true ;
1235+ break ;
1236+ }
1237+ }
1238+ if (foundSameOpSignature ) {
1239+ additionalBody .getContent ().put (contentType , mediaType );
1240+ break ;
1241+ }
1242+ }
1243+ }
1244+
1245+ mediaTypesToRemove .add (contentType );
1246+ if (groupByResponseContentType && foundSameOpSignature ) {
1247+ continue ;
1248+ }
1249+
1250+ var apiResponsesCopy = new ApiResponses ();
1251+ apiResponsesCopy .putAll (op .getResponses ());
1252+
1253+ additionalOps .add (new Operation ()
1254+ .deprecated (op .getDeprecated ())
1255+ .callbacks (op .getCallbacks ())
1256+ .description (op .getDescription ())
1257+ .extensions (op .getExtensions ())
1258+ .externalDocs (op .getExternalDocs ())
1259+ .operationId (getOrGenerateOperationId (op , path , httpMethod .name ()))
1260+ .parameters (op .getParameters ())
1261+ .responses (apiResponsesCopy )
1262+ .security (op .getSecurity ())
1263+ .servers (op .getServers ())
1264+ .summary (op .getSummary ())
1265+ .tags (op .getTags ())
1266+ .requestBody (new RequestBody ()
1267+ .description (body .getDescription ())
1268+ .extensions (body .getExtensions ())
1269+ .content (new Content ()
1270+ .addMediaType (contentType , mediaType ))
1271+ )
1272+ );
1273+ }
1274+ if (!mediaTypesToRemove .isEmpty ()) {
1275+ content .entrySet ().removeIf (stringMediaTypeEntry -> mediaTypesToRemove .contains (stringMediaTypeEntry .getKey ()));
1276+ }
1277+ }
1278+
1279+ private void divideOperationByResponses (
1280+ String path ,
1281+ PathItem .HttpMethod httpMethod ,
1282+ Operation op ,
1283+ Map <String , ApiResponses > apiResponsesByContentType ,
1284+ List <Operation > additionalOps
1285+ ) {
1286+ var isFirst = true ;
1287+ for (var entry : apiResponsesByContentType .entrySet ()) {
1288+ var contentType = entry .getKey ();
1289+ var apiResponses = entry .getValue ();
1290+ var requestBody = op .getRequestBody ();
1291+ // group by requestBody contentType
1292+ if (groupByRequestAndResponseContentType
1293+ && requestBody != null
1294+ && requestBody .getContent () != null
1295+ && !requestBody .getContent ().containsKey (contentType )) {
1296+ continue ;
1297+ }
1298+ if (isFirst ) {
1299+ op .setResponses (apiResponses );
1300+ isFirst = false ;
1301+ continue ;
1302+ }
1303+
1304+ additionalOps .add (new Operation ()
1305+ .deprecated (op .getDeprecated ())
1306+ .callbacks (op .getCallbacks ())
1307+ .description (op .getDescription ())
1308+ .extensions (op .getExtensions ())
1309+ .externalDocs (op .getExternalDocs ())
1310+ .operationId (getOrGenerateOperationId (op , path , httpMethod .name ()))
1311+ .parameters (op .getParameters ())
1312+ .responses (apiResponses )
1313+ .security (op .getSecurity ())
1314+ .servers (op .getServers ())
1315+ .summary (op .getSummary ())
1316+ .tags (op .getTags ())
1317+ .requestBody (requestBody )
1318+ );
1319+ }
1320+ }
1321+
10921322 // override with any special handling of the entire OpenAPI spec document
10931323 @ Override
10941324 @ SuppressWarnings ("unused" )
@@ -1188,8 +1418,7 @@ public String encodePath(String input) {
11881418 */
11891419 @ Override
11901420 public String escapeUnsafeCharacters (String input ) {
1191- LOGGER .warn ("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " +
1192- "unsafe characters" );
1421+ LOGGER .warn ("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape unsafe characters" );
11931422 // doing nothing by default and code generator should implement
11941423 // the logic to prevent code injection
11951424 // later we'll make this method abstract to make sure
@@ -1205,8 +1434,7 @@ public String escapeUnsafeCharacters(String input) {
12051434 */
12061435 @ Override
12071436 public String escapeQuotationMark (String input ) {
1208- LOGGER .warn ("escapeQuotationMark should be overridden in the code generator with proper logic to escape " +
1209- "single/double quote" );
1437+ LOGGER .warn ("escapeQuotationMark should be overridden in the code generator with proper logic to escape single/double quote" );
12101438 return input .replace ("\" " , "\\ \" " );
12111439 }
12121440
@@ -1779,6 +2007,12 @@ public DefaultCodegen() {
17792007 // option to change the order of form/body parameter
17802008 cliOptions .add (CliOption .newBoolean (CodegenConstants .PREPEND_FORM_OR_BODY_PARAMETERS ,
17812009 CodegenConstants .PREPEND_FORM_OR_BODY_PARAMETERS_DESC ).defaultValue (Boolean .FALSE .toString ()));
2010+ if (supportsDividingOperationsByContentType ()) {
2011+ cliOptions .add (CliOption .newBoolean (CodegenConstants .GROUP_BY_RESPONSE_CONTENT_TYPE ,
2012+ CodegenConstants .GROUP_BY_RESPONSE_CONTENT_TYPE_DESC ).defaultValue (Boolean .TRUE .toString ()));
2013+ cliOptions .add (CliOption .newBoolean (CodegenConstants .GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE ,
2014+ CodegenConstants .GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE_DESC ).defaultValue (Boolean .TRUE .toString ()));
2015+ }
17822016
17832017 // option to change how we process + set the data in the discriminator mapping
17842018 CliOption legacyDiscriminatorBehaviorOpt = CliOption .newBoolean (CodegenConstants .LEGACY_DISCRIMINATOR_BEHAVIOR , CodegenConstants .LEGACY_DISCRIMINATOR_BEHAVIOR_DESC ).defaultValue (Boolean .TRUE .toString ());
@@ -8596,11 +8830,16 @@ public boolean isTypeErasedGenerics() {
85968830 return false ;
85978831 }
85988832
8599- /*
8600- A function to convert yaml or json ingested strings like property names
8601- And convert special characters like newline, tab, carriage return
8602- Into strings that can be rendered in the language that the generator will output to
8603- */
8833+ @ Override
8834+ public boolean supportsDividingOperationsByContentType () {
8835+ return false ;
8836+ }
8837+
8838+ /**
8839+ * A function to convert yaml or json ingested strings like property names
8840+ * And convert special characters like newline, tab, carriage return
8841+ * Into strings that can be rendered in the language that the generator will output to
8842+ */
86048843 protected String handleSpecialCharacters (String name ) {
86058844 return name ;
86068845 }
0 commit comments