Merge e2e45e6385d74309833424401ceab0cc48137548 into d6c46342693205f0dae441b45742d9c85d41cf33

This commit is contained in:
altro3 2025-05-10 08:35:24 +02:00 committed by GitHub
commit f8b2103d3f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 844 additions and 26 deletions

View File

@ -69,6 +69,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|useJakartaEe|whether to use Jakarta EE namespace instead of javax| |false|
|useOneOfInterfaces|whether to use a java interface to describe a set of oneOf options, where each option is a class that implements the interface| |false|
|withXml|whether to include support for application/xml content type and include XML annotations in the model (works with libraries that provide support for JSON and XML)| |false|
|groupByResponseContentType| Group server or client methods by response content types. For example, when openapi operation produces one of "application/json" and "application/xml" content types will be generated only one method for both content types. Otherwise for each content type will be generated different method. **Available only for generatos with supportsDividingOperationsByContentType** | |true|
|groupByRequestAndResponseContentType| Group server or client methods by request body and response content types. For example, when openapi operation consumes "application/json" and "application/xml" content type and also api response has content with the same content types, 2 different methods will be generated. The content of the request and response types will match. Otherwise, will be generated 4 methods - for each combination of request body content type and response content type. **Available only for generatos with supportsDividingOperationsByContentType** | |true|
## SUPPORTED VENDOR EXTENSIONS

View File

@ -366,4 +366,5 @@ public interface CodegenConfig {
Set<String> getOpenapiGeneratorIgnoreList();
boolean supportsDividingOperationsByContentType();
}

View File

@ -454,4 +454,17 @@ public class CodegenConstants {
public static final String WAIT_TIME_OF_THREAD = "waitTimeMillis";
public static final String USE_DEFAULT_VALUES_FOR_REQUIRED_VARS = "useDefaultValuesForRequiredVars";
public static final String GROUP_BY_RESPONSE_CONTENT_TYPE = "groupByResponseContentType";
public static final String GROUP_BY_RESPONSE_CONTENT_TYPE_DESC =
"Group server or client methods by response content types. "
+ "For example, when openapi operation produces one of \"application/json\" and \"application/xml\" content types "
+ "will be generated only one method for both content types. Otherwise for each content type will be generated different method.";
public static final String GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE = "groupByRequestAndResponseContentType";
public static final String GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE_DESC =
"Group server or client methods by request body and response content types. "
+ "For example, when openapi operation consumes \"application/json\" and \"application/xml\" content type and also api response "
+ "has content with the same content types, 2 different methods will be generated. The content of the request and response types will match. "
+ "Otherwise, will be generated 4 methods - for each combination of request body content type and response content type.";
}

View File

@ -333,6 +333,10 @@ public class DefaultCodegen implements CodegenConfig {
// Whether to automatically hardcode params that are considered Constants by OpenAPI Spec
@Setter protected boolean autosetConstants = false;
@Setter
protected boolean groupByRequestAndResponseContentType = true;
@Setter
protected boolean groupByResponseContentType = true;
@Override
public boolean getAddSuffixToDuplicateOperationNicknames() {
@ -392,9 +396,10 @@ public class DefaultCodegen implements CodegenConfig {
convertPropertyToBooleanAndWriteBack(CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT, this::setDisallowAdditionalPropertiesIfNotPresent);
convertPropertyToBooleanAndWriteBack(CodegenConstants.ENUM_UNKNOWN_DEFAULT_CASE, this::setEnumUnknownDefaultCase);
convertPropertyToBooleanAndWriteBack(CodegenConstants.AUTOSET_CONSTANTS, this::setAutosetConstants);
convertPropertyToBooleanAndWriteBack(CodegenConstants.GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE, this::setGroupByRequestAndResponseContentType);
convertPropertyToBooleanAndWriteBack(CodegenConstants.GROUP_BY_RESPONSE_CONTENT_TYPE, this::setGroupByResponseContentType);
}
/***
* Preset map builder with commonly used Mustache lambdas.
*
@ -910,7 +915,7 @@ public class DefaultCodegen implements CodegenConfig {
* @return the sanitized variable name for enum
*/
public String toEnumVarName(String value, String datatype) {
if (value.length() == 0) {
if (value.isEmpty()) {
return "EMPTY";
}
@ -1011,6 +1016,47 @@ public class DefaultCodegen implements CodegenConfig {
@Override
@SuppressWarnings("unused")
public void preprocessOpenAPI(OpenAPI openAPI) {
if (supportsDividingOperationsByContentType() && openAPI.getPaths() != null && !openAPI.getPaths().isEmpty()) {
for (Map.Entry<String, PathItem> entry : openAPI.getPaths().entrySet()) {
String pathStr = entry.getKey();
PathItem path = entry.getValue();
List<Operation> getOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.GET, path.getGet());
if (!getOps.isEmpty()) {
path.addExtension("x-get", getOps);
}
List<Operation> putOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.PUT, path.getPut());
if (!putOps.isEmpty()) {
path.addExtension("x-put", putOps);
}
List<Operation> postOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.POST, path.getPost());
if (!postOps.isEmpty()) {
path.addExtension("x-post", postOps);
}
List<Operation> deleteOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.DELETE, path.getDelete());
if (!deleteOps.isEmpty()) {
path.addExtension("x-delete", deleteOps);
}
List<Operation> optionsOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.OPTIONS, path.getOptions());
if (!optionsOps.isEmpty()) {
path.addExtension("x-options", optionsOps);
}
List<Operation> headOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.HEAD, path.getHead());
if (!headOps.isEmpty()) {
path.addExtension("x-head", headOps);
}
List<Operation> patchOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.PATCH, path.getPatch());
if (!patchOps.isEmpty()) {
path.addExtension("x-patch", patchOps);
}
List<Operation> traceOps = divideOperationsByContentType(openAPI, pathStr, PathItem.HttpMethod.TRACE, path.getTrace());
if (!traceOps.isEmpty()) {
path.addExtension("x-trace", traceOps);
}
}
}
if (useOneOfInterfaces && openAPI.getComponents() != null) {
// we process the openapi schema here to find oneOf schemas and create interface models for them
Map<String, Schema> schemas = new HashMap<>(openAPI.getComponents().getSchemas());
@ -1092,6 +1138,261 @@ public class DefaultCodegen implements CodegenConfig {
}
}
private List<Operation> divideOperationsByContentType(OpenAPI openAPI, String path, PathItem.HttpMethod httpMethod, Operation op) {
if (op == null) {
return Collections.emptyList();
}
var operationIndexes = new HashMap<String, Integer>();
operationIndexes.put(getOrGenerateOperationId(op, path, httpMethod.name()), 0);
var additionalOps = new ArrayList<Operation>();
divideOperationByRequestBody(openAPI, path, httpMethod, op, additionalOps, operationIndexes);
// Check responses content types and divide operations by them
var responses = op.getResponses();
if (responses == null || responses.isEmpty()) {
return additionalOps;
}
var unwrappedResponses = new ApiResponses();
var allPossibleContentTypes = new ArrayList<String>();
for (var responseEntry : responses.entrySet()) {
var apiResponse = ModelUtils.getReferencedApiResponse(openAPI, responseEntry.getValue());
unwrappedResponses.put(responseEntry.getKey(), apiResponse);
if (apiResponse.getContent() == null) {
continue;
}
for (var contentType : apiResponse.getContent().keySet()) {
contentType = contentType.toLowerCase();
if (!allPossibleContentTypes.contains(contentType)) {
allPossibleContentTypes.add(contentType);
}
}
}
if (allPossibleContentTypes.isEmpty() || allPossibleContentTypes.size() == 1) {
return additionalOps;
}
op.setResponses(unwrappedResponses);
responses = unwrappedResponses;
var apiResponsesByContentType = new HashMap<String, ApiResponses>();
for (var contentType : allPossibleContentTypes) {
var apiResponses = new ApiResponses();
for (var responseEntry : responses.entrySet()) {
var code = responseEntry.getKey();
var response = responseEntry.getValue();
if (response.getContent() == null) {
continue;
}
var mediaType = response.getContent().get(contentType);
if (mediaType == null) {
continue;
}
apiResponses.addApiResponse(code, new ApiResponse()
.description(response.getDescription())
.headers(response.getHeaders())
.links(response.getLinks())
.extensions(response.getExtensions() != null ? new LinkedHashMap<>(response.getExtensions()) : new LinkedHashMap<>())
.$ref(response.get$ref())
.content(new Content()
.addMediaType(contentType, mediaType)
)
);
}
apiResponsesByContentType.put(contentType, apiResponses);
}
var addedContentTypes = new ArrayList<String>();
var finalAdditionalOps = new ArrayList<Operation>();
divideOperationByResponses(path, httpMethod, op, apiResponsesByContentType, finalAdditionalOps, addedContentTypes, operationIndexes);
for (var additionalOp : additionalOps) {
finalAdditionalOps.add(additionalOp);
divideOperationByResponses(path, httpMethod, additionalOp, apiResponsesByContentType, finalAdditionalOps, addedContentTypes, operationIndexes);
}
// remove correct processed contentTypes
apiResponsesByContentType.entrySet().removeIf(stringMediaTypeEntry -> addedContentTypes.contains(stringMediaTypeEntry.getKey()));
if (!apiResponsesByContentType.isEmpty()) {
appendCommonResponseMediaTypes(op, apiResponsesByContentType.values());
for (var additionalOp : additionalOps) {
appendCommonResponseMediaTypes(additionalOp, apiResponsesByContentType.values());
}
}
return finalAdditionalOps;
}
private void divideOperationByRequestBody(OpenAPI openAPI, String path, PathItem.HttpMethod httpMethod, Operation op,
List<Operation> additionalOps, Map<String, Integer> operationIndexes) {
RequestBody body = ModelUtils.getReferencedRequestBody(openAPI, op.getRequestBody());
if (body == null || body.getContent() == null) {
return;
}
op.setRequestBody(body);
Content content = body.getContent();
if (content.size() <= 1) {
return;
}
var firstEntry = content.entrySet().iterator().next();
var mediaTypesToRemove = new ArrayList<String>();
for (var entry : content.entrySet()) {
var contentType = entry.getKey();
MediaType mediaType = entry.getValue();
if (mediaTypesToRemove.contains(contentType) || contentType.equals(firstEntry.getKey())) {
continue;
}
var foundSameOpSignature = false;
// group by response content type
if (groupByResponseContentType) {
if (firstEntry.getValue().equals(mediaType)) {
if (!groupByRequestAndResponseContentType) {
foundSameOpSignature = true;
}
} else {
for (var additionalOp : additionalOps) {
RequestBody additionalBody = ModelUtils.getReferencedRequestBody(openAPI, additionalOp.getRequestBody());
if (additionalBody == null || additionalBody.getContent() == null) {
return;
}
for (var addContentEntry : additionalBody.getContent().entrySet()) {
if (addContentEntry.getValue().equals(mediaType)) {
foundSameOpSignature = true;
break;
}
}
if (foundSameOpSignature) {
additionalBody.getContent().put(contentType, mediaType);
break;
}
}
}
}
if (groupByResponseContentType && foundSameOpSignature) {
continue;
}
mediaTypesToRemove.add(contentType);
var apiResponsesCopy = new ApiResponses();
apiResponsesCopy.putAll(op.getResponses());
additionalOps.add(new Operation()
.deprecated(op.getDeprecated())
.callbacks(op.getCallbacks())
.description(op.getDescription())
.extensions(op.getExtensions() != null ? new LinkedHashMap<>(op.getExtensions()) : new LinkedHashMap<>())
.externalDocs(op.getExternalDocs())
.operationId(calcOperationId(path, httpMethod, op, operationIndexes))
.parameters(op.getParameters())
.responses(apiResponsesCopy)
.security(op.getSecurity())
.servers(op.getServers())
.summary(op.getSummary())
.tags(op.getTags())
.requestBody(new RequestBody()
.description(body.getDescription())
.extensions(body.getExtensions())
.content(new Content()
.addMediaType(contentType, mediaType))
)
);
}
if (!mediaTypesToRemove.isEmpty()) {
content.entrySet().removeIf(stringMediaTypeEntry -> mediaTypesToRemove.contains(stringMediaTypeEntry.getKey()));
}
}
private String calcOperationId(String path, PathItem.HttpMethod httpMethod, Operation op, Map<String, Integer> operationIndexes) {
var operationId = getOrGenerateOperationId(op, path, httpMethod.name());
var index = operationIndexes.get(operationId);
if (index != null) {
index++;
operationId += "_" + index;
operationIndexes.put(operationId, index);
} else {
operationIndexes.put(operationId, 0);
}
return operationId;
}
private void divideOperationByResponses(
String path,
PathItem.HttpMethod httpMethod,
Operation op,
Map<String, ApiResponses> apiResponsesByContentType,
List<Operation> additionalOps,
List<String> addedContentTypes,
Map<String, Integer> operationIndexes
) {
if (!groupByResponseContentType) {
return;
}
var isFirst = true;
for (var entry : apiResponsesByContentType.entrySet()) {
var contentType = entry.getKey();
var apiResponses = entry.getValue();
var requestBody = op.getRequestBody();
// group by requestBody contentType
if (groupByRequestAndResponseContentType
&& requestBody != null
&& requestBody.getContent() != null
&& !requestBody.getContent().containsKey(contentType)) {
continue;
}
addedContentTypes.add(contentType);
if (isFirst) {
op.setResponses(apiResponses);
isFirst = false;
continue;
}
additionalOps.add(new Operation()
.deprecated(op.getDeprecated())
.callbacks(op.getCallbacks())
.description(op.getDescription())
.extensions(op.getExtensions())
.externalDocs(op.getExternalDocs())
.operationId(calcOperationId(path, httpMethod, op, operationIndexes))
.parameters(op.getParameters())
.responses(apiResponses)
.security(op.getSecurity())
.servers(op.getServers())
.summary(op.getSummary())
.tags(op.getTags())
.requestBody(requestBody)
);
}
}
private void appendCommonResponseMediaTypes(Operation op, Collection<ApiResponses> commonResponsesList) {
if (commonResponsesList.isEmpty()) {
return;
}
for (var commonResponses : commonResponsesList) {
var adOpResponses = op.getResponses();
for (var responseEntry : adOpResponses.entrySet()) {
var content = responseEntry.getValue().getContent();
var commonResponse = commonResponses.get(responseEntry.getKey());
if (commonResponse != null && commonResponse.getContent() != null) {
if (content == null) {
content = new Content();
}
for (var commonResponseEntry : commonResponse.getContent().entrySet()) {
if (!content.containsKey(commonResponseEntry.getKey())) {
content.addMediaType(commonResponseEntry.getKey(), commonResponseEntry.getValue());
}
}
}
}
}
}
// override with any special handling of the entire OpenAPI spec document
@Override
@SuppressWarnings("unused")
@ -1191,8 +1492,7 @@ public class DefaultCodegen implements CodegenConfig {
*/
@Override
public String escapeUnsafeCharacters(String input) {
LOGGER.warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape " +
"unsafe characters");
LOGGER.warn("escapeUnsafeCharacters should be overridden in the code generator with proper logic to escape unsafe characters");
// doing nothing by default and code generator should implement
// the logic to prevent code injection
// later we'll make this method abstract to make sure
@ -1208,8 +1508,7 @@ public class DefaultCodegen implements CodegenConfig {
*/
@Override
public String escapeQuotationMark(String input) {
LOGGER.warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape " +
"single/double quote");
LOGGER.warn("escapeQuotationMark should be overridden in the code generator with proper logic to escape single/double quote");
return input.replace("\"", "\\\"");
}
@ -1782,6 +2081,12 @@ public class DefaultCodegen implements CodegenConfig {
// option to change the order of form/body parameter
cliOptions.add(CliOption.newBoolean(CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS,
CodegenConstants.PREPEND_FORM_OR_BODY_PARAMETERS_DESC).defaultValue(Boolean.FALSE.toString()));
if (supportsDividingOperationsByContentType()) {
cliOptions.add(CliOption.newBoolean(CodegenConstants.GROUP_BY_RESPONSE_CONTENT_TYPE,
CodegenConstants.GROUP_BY_RESPONSE_CONTENT_TYPE_DESC).defaultValue(Boolean.TRUE.toString()));
cliOptions.add(CliOption.newBoolean(CodegenConstants.GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE,
CodegenConstants.GROUP_BY_REQUEST_AND_RESPONSE_CONTENT_TYPE_DESC).defaultValue(Boolean.TRUE.toString()));
}
// option to change how we process + set the data in the discriminator mapping
CliOption legacyDiscriminatorBehaviorOpt = CliOption.newBoolean(CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR, CodegenConstants.LEGACY_DISCRIMINATOR_BEHAVIOR_DESC).defaultValue(Boolean.TRUE.toString());
@ -8591,10 +8896,15 @@ public class DefaultCodegen implements CodegenConfig {
return false;
}
/*
A function to convert yaml or json ingested strings like property names
And convert special characters like newline, tab, carriage return
Into strings that can be rendered in the language that the generator will output to
@Override
public boolean supportsDividingOperationsByContentType() {
return false;
}
/**
* A function to convert yaml or json ingested strings like property names
* And convert special characters like newline, tab, carriage return
* Into strings that can be rendered in the language that the generator will output to
*/
protected String handleSpecialCharacters(String name) {
return name;

View File

@ -606,10 +606,10 @@ public class DefaultGenerator implements Generator {
if (!processedModels.contains(key) && allSchemas.containsKey(key)) {
generateModels(files, allModels, unusedModels, aliasModels, processedModels, () -> Set.of(key));
} else {
LOGGER.info("Type " + variable.getComplexType() + " of variable " + variable.getName() + " could not be resolve because it is not declared as a model.");
LOGGER.info("Type {} of variable {} could not be resolve because it is not declared as a model.", variable.getComplexType(), variable.getName());
}
} else {
LOGGER.info("Type " + variable.getOpenApiType() + " of variable " + variable.getName() + " could not be resolve because it is not declared as a model.");
LOGGER.info("Type {} of variable {} could not be resolve because it is not declared as a model.", variable.getComplexType(), variable.getName());
}
}
@ -1002,7 +1002,7 @@ public class DefaultGenerator implements Generator {
File ignoreFile = new File(ignoreFileNameTarget);
// use the entries provided by the users to pre-populate .openapi-generator-ignore
try {
LOGGER.info("Writing file " + ignoreFileNameTarget + " (which is always overwritten when the option `openapiGeneratorIgnoreFile` is enabled.)");
LOGGER.info("Writing file {} (which is always overwritten when the option `openapiGeneratorIgnoreFile` is enabled.)", ignoreFileNameTarget);
new File(config.outputFolder()).mkdirs();
if (!ignoreFile.createNewFile()) {
// file may already exist, do nothing
@ -1465,6 +1465,9 @@ public class DefaultGenerator implements Generator {
if (paths == null) {
return ops;
}
var divideOperationsByContentType = config.supportsDividingOperationsByContentType();
for (Map.Entry<String, PathItem> pathsEntry : paths.entrySet()) {
String resourcePath = pathsEntry.getKey();
PathItem path = pathsEntry.getValue();
@ -1476,10 +1479,34 @@ public class DefaultGenerator implements Generator {
processOperation(resourcePath, "patch", path.getPatch(), ops, path);
processOperation(resourcePath, "options", path.getOptions(), ops, path);
processOperation(resourcePath, "trace", path.getTrace(), ops, path);
if (divideOperationsByContentType) {
processAdditionalOperations(resourcePath, "x-get", "get", ops, path);
processAdditionalOperations(resourcePath, "x-head", "head", ops, path);
processAdditionalOperations(resourcePath, "x-put", "put", ops, path);
processAdditionalOperations(resourcePath, "x-post", "post", ops, path);
processAdditionalOperations(resourcePath, "x-delete", "delete", ops, path);
processAdditionalOperations(resourcePath, "x-patch", "patch", ops, path);
processAdditionalOperations(resourcePath, "x-options", "options", ops, path);
processAdditionalOperations(resourcePath, "x-trace", "trace", ops, path);
}
}
return ops;
}
protected void processAdditionalOperations(String resourcePath, String extName, String httpMethod, Map<String, List<CodegenOperation>> ops, PathItem path) {
if (path.getExtensions() == null || !path.getExtensions().containsKey(extName)) {
return;
}
var xOps = (List<Operation>) path.getExtensions().get(extName);
if (xOps == null) {
return;
}
for (Operation op : xOps) {
processOperation(resourcePath, httpMethod, op, ops, path);
}
}
public Map<String, List<CodegenOperation>> processWebhooks(Map<String, PathItem> webhooks) {
Map<String, List<CodegenOperation>> ops = new TreeMap<>();
// when input file is not valid and doesn't contain any paths

View File

@ -2461,4 +2461,9 @@ public abstract class AbstractJavaCodegen extends DefaultCodegen implements Code
throw new RuntimeException(sb.toString());
}
}
@Override
public boolean supportsDividingOperationsByContentType() {
return true;
}
}

View File

@ -27,6 +27,7 @@ import lombok.Setter;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.config.GlobalSettings;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
import org.openapitools.codegen.templating.mustache.EscapeChar;
@ -1157,4 +1158,9 @@ public abstract class AbstractKotlinCodegen extends DefaultCodegen implements Co
}
}
}
@Override
public boolean supportsDividingOperationsByContentType() {
return true;
}
}

View File

@ -1004,7 +1004,7 @@ public class SpringCodegen extends AbstractJavaCodegen
// add Pageable import only if x-spring-paginated explicitly used
// this allows to use a custom Pageable schema without importing Spring Pageable.
if (Boolean.TRUE.equals(operation.getExtensions().get("x-spring-paginated"))) {
if (operation.getExtensions() != null && Boolean.TRUE.equals(operation.getExtensions().get("x-spring-paginated"))) {
importMapping.put("Pageable", "org.springframework.data.domain.Pageable");
}
@ -1093,7 +1093,7 @@ public class SpringCodegen extends AbstractJavaCodegen
private Set<String> reformatProvideArgsParams(Operation operation) {
Set<String> provideArgsClassSet = new HashSet<>();
Object argObj = operation.getExtensions().get("x-spring-provide-args");
Object argObj = operation.getExtensions() != null ? operation.getExtensions().get("x-spring-provide-args") : null;
if (argObj instanceof List) {
List<String> provideArgs = (List<String>) argObj;
if (!provideArgs.isEmpty()) {
@ -1122,7 +1122,7 @@ public class SpringCodegen extends AbstractJavaCodegen
formattedArgs.add(newArg);
}
}
operation.getExtensions().put("x-spring-provide-args", formattedArgs);
operation.addExtension("x-spring-provide-args", formattedArgs);
}
}
return provideArgsClassSet;

View File

@ -20,7 +20,6 @@ import static java.util.stream.Collectors.groupingBy;
import static org.openapitools.codegen.TestUtils.newTempFolder;
import static org.testng.Assert.assertEquals;
public class JavaMicronautClientCodegenTest extends AbstractMicronautCodegenTest {
@Test
public void clientOptsUnicity() {
@ -457,4 +456,320 @@ public class JavaMicronautClientCodegenTest extends AbstractMicronautCodegenTest
.hasAnnotation("JacksonXmlProperty", Map.of("localName", "\"item\""))
.hasAnnotation("JacksonXmlElementWrapper", Map.of("localName", "\"activities-array\""));
}
@Test
public void testMultipleContentTypesToPathTrueTrue() {
var codegen = new JavaMicronautClientCodegen();
codegen.setGroupByRequestAndResponseContentType(true);
codegen.setGroupByResponseContentType(true);
String outputPath = generateFiles(codegen, "src/test/resources/3_0/java/multiple-content-types.yaml", CodegenConstants.APIS, CodegenConstants.MODELS);
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/DefaultApi.java",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<Coordinates> myOp(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<Coordinates> myOp_1(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\", \"application/xml\", \"text/json\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<Coordinates> myOp_2(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/yaml\", \"text/json\"})\n" +
" Mono<MySchema> myOp_3(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );"
);
}
@Test
public void testMultipleContentTypesToPathTrueFalse() {
var codegen = new JavaMicronautClientCodegen();
codegen.setGroupByRequestAndResponseContentType(true);
codegen.setGroupByResponseContentType(false);
String outputPath = generateFiles(codegen, "src/test/resources/3_0/java/multiple-content-types.yaml", CodegenConstants.APIS, CodegenConstants.MODELS);
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/DefaultApi.java",
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<Coordinates> myOp(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<Coordinates> myOp_1(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\", \"application/xml\", \"text/json\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<Coordinates> myOp_2(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\", \"application/xml\", \"text/json\"})\n" +
" @Produces({\"application/yaml\"})\n" +
" Mono<Coordinates> myOp_3(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"text/json\"})\n" +
" Mono<MySchema> myOp_4(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );"
);
}
@Test
public void testMultipleContentTypesToPathFalseTrue() {
var codegen = new JavaMicronautClientCodegen();
codegen.setGroupByRequestAndResponseContentType(false);
codegen.setGroupByResponseContentType(true);
String outputPath = generateFiles(codegen, "src/test/resources/3_0/java/multiple-content-types.yaml", CodegenConstants.APIS, CodegenConstants.MODELS);
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/DefaultApi.java",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<Coordinates> myOp(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<Coordinates> myOp_1(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<MySchema> myOp_2(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/yaml\", \"text/json\"})\n" +
" Mono<MySchema> myOp_3(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<Coordinates> myOp_4(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<Coordinates> myOp_5(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<MySchema> myOp_6(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<Coordinates> myOp_7(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<Coordinates> myOp_8(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<MySchema> myOp_9(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/yaml\", \"text/json\"})\n" +
" Mono<Coordinates> myOp_10(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/yaml\", \"text/json\"})\n" +
" Mono<Coordinates> myOp_11(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );"
);
}
@Test
public void testMultipleContentTypesToPathFalseFalse() {
var codegen = new JavaMicronautClientCodegen();
codegen.setGroupByRequestAndResponseContentType(false);
codegen.setGroupByResponseContentType(false);
String outputPath = generateFiles(codegen, "src/test/resources/3_0/java/multiple-content-types.yaml", CodegenConstants.APIS, CodegenConstants.MODELS);
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/DefaultApi.java",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<Coordinates> myOp(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<Coordinates> myOp_1(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/json\"})\n" +
" Mono<MySchema> myOp_2(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/yaml\"})\n" +
" Mono<MySchema> myOp_3(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"text/json\"})\n" +
" Mono<Coordinates> myOp_4(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"text/json\"})\n" +
" Mono<Coordinates> myOp_5(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"text/json\"})\n" +
" Mono<MySchema> myOp_6(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<Coordinates> myOp_7(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<Coordinates> myOp_8(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"application/xml\"})\n" +
" Mono<MySchema> myOp_9(\n" +
" @Body @Nullable @Valid Coordinates coordinates\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<Coordinates> myOp_10(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<Coordinates> myOp_11(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"text/json\"})\n" +
" @Produces({\"multipart/form-data\"})\n" +
" Mono<MySchema> myOp_12(\n" +
" @Nullable @Valid Coordinates coordinates, \n" +
" @Nullable File _file\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/xml\"})\n" +
" @Produces({\"application/yaml\"})\n" +
" Mono<Coordinates> myOp_13(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );",
" @Post(uri=\"/multiplecontentpath\")\n" +
" @Consumes({\"application/json\"})\n" +
" @Produces({\"application/yaml\"})\n" +
" Mono<Coordinates> myOp_14(\n" +
" @Body @Nullable @Valid MySchema mySchema\n" +
" );"
);
}
@Test
public void testMultipleContentTypesWithRefs() {
var codegen = new JavaMicronautClientCodegen();
codegen.setGroupByRequestAndResponseContentType(true);
codegen.setGroupByResponseContentType(true);
String outputPath = generateFiles(codegen, "src/test/resources/3_0/java/multiple-content-types-2.yaml", CodegenConstants.APIS, CodegenConstants.MODELS);
assertFileContains(outputPath + "/src/main/java/org/openapitools/api/DefaultApi.java",
""
);
}
}

View File

@ -487,4 +487,5 @@ public class JavaMicronautServerCodegenTest extends AbstractMicronautCodegenTest
.hasAnnotation("JacksonXmlProperty", Map.of("localName", "\"item\""))
.hasAnnotation("JacksonXmlElementWrapper", Map.of("localName", "\"activities-array\""));
}
}

View File

@ -0,0 +1,71 @@
openapi: 3.0.3
info:
version: "1"
title: Multiple Content Types for same request
paths:
/pet:
post:
tags:
- pet
summary: Add a new pet to the store
description: ''
operationId: addPet
responses:
'200':
$ref: "#/components/responses/200"
'405':
description: Invalid input
security:
- petstore_auth:
- 'write:pets'
- 'read:pets'
requestBody:
$ref: '#/components/requestBodies/Pet'
components:
responses:
'200':
description: successful operation
content:
application/xml:
schema:
$ref: '#/components/schemas/Pet'
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/yaml:
schema:
$ref: '#/components/schemas/MySchema'
requestBodies:
Pet:
content:
application/json:
schema:
$ref: '#/components/schemas/Pet'
application/xml:
schema:
$ref: '#/components/schemas/Pet'
text/json:
schema:
$ref: '#/components/schemas/MySchema'
description: Pet object that needs to be added to the store
required: true
schemas:
MySchema:
type: object
properties:
id:
type: string
Pet:
title: a Pet
description: A pet for sale in the pet store
type: object
required:
- name
- photoUrls
properties:
id:
type: integer
format: int64
name:
type: string
example: doggie

View File

@ -0,0 +1,67 @@
openapi: 3.0.3
info:
version: "1"
title: Multiple Content Types for same request
paths:
/multiplecontentpath:
post:
operationId: myOp
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/coordinates'
application/xml:
schema:
$ref: '#/components/schemas/coordinates'
multipart/form-data:
schema:
type: object
properties:
coordinates:
$ref: '#/components/schemas/coordinates'
file:
type: string
format: binary
application/yaml:
schema:
$ref: '#/components/schemas/MySchema'
text/json:
schema:
$ref: '#/components/schemas/MySchema'
responses:
201:
description: Successfully created
headers:
Location:
schema:
type: string
content:
application/json:
schema:
$ref: '#/components/schemas/coordinates'
application/xml:
schema:
$ref: '#/components/schemas/coordinates'
text/json:
schema:
$ref: '#/components/schemas/MySchema'
components:
schemas:
coordinates:
type: object
required:
- lat
- long
properties:
lat:
type: number
long:
type: number
MySchema:
type: object
required:
- lat
properties:
lat:
type: number