Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1052,6 +1052,8 @@ public boolean specVersionGreaterThanOrEqualTo310(OpenAPI openAPI) {
return specMajorVersion == 3 && specMinorVersion >= 1;
}

private List<String> schemasUsedOnlyInFormParam = null;

/**
* Set the OpenAPI document.
* This method is invoked when the input OpenAPI document has been parsed and validated.
Expand All @@ -1062,6 +1064,7 @@ public void setOpenAPI(OpenAPI openAPI) {
once(LOGGER).warn(UNSUPPORTED_V310_SPEC_MSG);
}
this.openAPI = openAPI;
this.schemasUsedOnlyInFormParam = null;
// Set global settings such that helper functions in ModelUtils can lookup the value
// of the CLI option.
ModelUtils.setDisallowAdditionalPropertiesIfNotPresent(getDisallowAdditionalPropertiesIfNotPresent());
Expand All @@ -1070,6 +1073,21 @@ public void setOpenAPI(OpenAPI openAPI) {
typeAliases = getAllAliases(ModelUtils.getSchemas(openAPI));
}

/**
* Returns the list of schema names that are used only in form parameters.
*
* @return list of schema names used only in form parameters
*/
public List<String> getSchemasUsedOnlyInFormParam() {
if (this.openAPI == null) {
return Collections.emptyList();
}
if (this.schemasUsedOnlyInFormParam == null) {
this.schemasUsedOnlyInFormParam = ModelUtils.getSchemasUsedOnlyInFormParam(this.openAPI);
}
return this.schemasUsedOnlyInFormParam;
}

// override with any message to be shown right before the process finishes
@Override
@SuppressWarnings("static-method")
Expand Down Expand Up @@ -8214,6 +8232,36 @@ private CodegenParameter headerToCodegenParameter(Header header, String headerNa
return param;
}

/**
* Check if the content type is form data (e.g. multipart/* or application/x-www-form-urlencoded).
*
* @param contentType Content type string
* @return true if form content type
*/
protected boolean isFormContentType(String contentType) {
if (contentType == null) {
return false;
}
String ct = contentType.toLowerCase(Locale.ROOT);
return ct.startsWith("application/x-www-form-urlencoded") || ct.startsWith("multipart");
}

/**
* Check if skipFormModel is enabled (defaults to true).
*
* @return true if skipFormModel is true
*/
public boolean isSkipFormModel() {
if (additionalProperties.containsKey(CodegenConstants.SKIP_FORM_MODEL)) {
final Object val = additionalProperties.get(CodegenConstants.SKIP_FORM_MODEL);
if (val instanceof Boolean) {
return (Boolean) val;
}
return Boolean.parseBoolean(val.toString());
}
return Boolean.parseBoolean(GlobalSettings.getProperty(CodegenConstants.SKIP_FORM_MODEL, "true"));
}

protected LinkedHashMap<String, CodegenMediaType> getContent(Content content, Set<String> imports, String mediaTypeSchemaSuffix) {
if (content == null) {
return null;
Expand Down Expand Up @@ -8286,7 +8334,17 @@ protected LinkedHashMap<String, CodegenMediaType> getContent(Content content, Se

cmtContent.put(contentType, codegenMt);
if (schemaProp != null) {
addImports(imports, schemaProp.getImports(true, importBaseType, generatorMetadata.getFeatureSet()));
Set<String> propImports = schemaProp.getImports(true, importBaseType, generatorMetadata.getFeatureSet());
if (isFormContentType(contentType) && isSkipFormModel()) {
List<String> formOnlySchemas = getSchemasUsedOnlyInFormParam();
Set<String> formOnlyModels = formOnlySchemas.stream()
.flatMap(s -> Stream.of(s, toModelName(s)))
.collect(Collectors.toSet());
propImports = propImports.stream()
.filter(imp -> !formOnlyModels.contains(imp))
.collect(Collectors.toSet());
}
addImports(imports, propImports);
}
}
return cmtContent;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,14 @@ private Boolean getGeneratorPropertyDefaultSwitch(final String key, final Boolea
return defaultValue;
}

private Boolean getGlobalOrGeneratorPropertyDefault(final String key, final Boolean defaultValue) {
String global = GlobalSettings.getProperty(key);
if (global != null) {
return Boolean.valueOf(global);
}
return getGeneratorPropertyDefaultSwitch(key, defaultValue);
}

void configureGeneratorProperties() {
// allows generating only models by specifying a CSV of models to generate, or empty for all
// NOTE: Boolean.TRUE is required below rather than `true` because of JVM boxing constraints and type inference.
Expand Down Expand Up @@ -219,11 +227,12 @@ void configureGeneratorProperties() {
}
// model/api tests and documentation options rely on parent generate options (api or model) and no other options.
// They default to true in all scenarios and can only be marked false explicitly
generateModelTests = GlobalSettings.getProperty(CodegenConstants.MODEL_TESTS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.MODEL_TESTS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.MODEL_TESTS, true);
generateModelDocumentation = GlobalSettings.getProperty(CodegenConstants.MODEL_DOCS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.MODEL_DOCS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.MODEL_DOCS, true);
generateApiTests = GlobalSettings.getProperty(CodegenConstants.API_TESTS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.API_TESTS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.API_TESTS, true);
generateApiDocumentation = GlobalSettings.getProperty(CodegenConstants.API_DOCS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.API_DOCS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.API_DOCS, true);
generateRecursiveDependentModels = GlobalSettings.getProperty(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS) != null ? Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS)) : getGeneratorPropertyDefaultSwitch(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS, false);
generateModelTests = getGlobalOrGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, true);
generateModelDocumentation = getGlobalOrGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, true);
generateApiTests = getGlobalOrGeneratorPropertyDefault(CodegenConstants.API_TESTS, true);
generateApiDocumentation = getGlobalOrGeneratorPropertyDefault(CodegenConstants.API_DOCS, true);
generateRecursiveDependentModels = getGlobalOrGeneratorPropertyDefault(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS, false);
Boolean skipFormModel = getGlobalOrGeneratorPropertyDefault(CodegenConstants.SKIP_FORM_MODEL, true);

// Additional properties added for tests to exclude references in project related files
config.additionalProperties().put(CodegenConstants.GENERATE_API_TESTS, generateApiTests);
Expand All @@ -236,6 +245,7 @@ void configureGeneratorProperties() {
config.additionalProperties().put(CodegenConstants.GENERATE_MODELS, generateModels);
config.additionalProperties().put(CodegenConstants.GENERATE_WEBHOOKS, generateWebhooks);
config.additionalProperties().put(CodegenConstants.GENERATE_RECURSIVE_DEPENDENT_MODELS, generateRecursiveDependentModels);
config.additionalProperties().put(CodegenConstants.SKIP_FORM_MODEL, skipFormModel);

if (!generateApiTests && !generateModelTests) {
config.additionalProperties().put(CodegenConstants.EXCLUDE_TESTS, true);
Expand Down Expand Up @@ -470,9 +480,9 @@ void generateModels(List<File> files, List<ModelMap> allModels, List<String> unu
// store all processed models
Map<String, ModelsMap> allProcessedModels = new TreeMap<>((o1, o2) -> ObjectUtils.compare(config.toModelName(o1), config.toModelName(o2)));

Boolean skipFormModel = GlobalSettings.getProperty(CodegenConstants.SKIP_FORM_MODEL) != null ?
Boolean.valueOf(GlobalSettings.getProperty(CodegenConstants.SKIP_FORM_MODEL)) :
getGeneratorPropertyDefaultSwitch(CodegenConstants.SKIP_FORM_MODEL, true);
Boolean skipFormModel = config.additionalProperties().containsKey(CodegenConstants.SKIP_FORM_MODEL) ?
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SubhamAshok Same as above. This looks way too convulated

Boolean.valueOf(config.additionalProperties().get(CodegenConstants.SKIP_FORM_MODEL).toString()) :
Boolean.TRUE;

// process models only
for (String name : modelKeys) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9335,4 +9335,70 @@ public void issue_24232() throws IOException {
JavaFileAssert.assertThat(files.get("Dummy.java"))
.fileContains("import org.myorg.MyCustomId;", "import org.myorg.MyCustomKey;");
}

@Test
public void testMultipleRequestBodyContentTypesDanglingImport_issue24727() throws Exception {
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(INTERFACE_ONLY, "true");
additionalProperties.put(USE_TAGS, "true");

Map<String, File> files = generateFromContract(
"src/test/resources/3_0/issue_24727.yaml", SPRING_BOOT,
additionalProperties);

JavaFileAssert.assertThat(files.get("DefaultApi.java"))
.fileContains("import org.openapitools.model.MultiArticleImporter;")
.fileDoesNotContain("import org.openapitools.model.CreateMultiArticleImporterRequest;");

Assert.assertNotNull(files.get("MultiArticleImporter.java"));
Assert.assertNull(files.get("CreateMultiArticleImporterRequest.java"));
}

@Test
public void testMultipleRequestBodyContentTypesWithSkipFormModelFalse_issue24727() throws Exception {
File output = Files.createTempDirectory("test").toFile().getCanonicalFile();
output.deleteOnExit();

OpenAPI openAPI = new OpenAPIParser()
.readLocation("src/test/resources/3_0/issue_24727.yaml", null, new ParseOptions()).getOpenAPI();

SpringCodegen codegen = new SpringCodegen();
codegen.setOutputDir(output.getAbsolutePath());
codegen.additionalProperties().put(INTERFACE_ONLY, "true");
codegen.additionalProperties().put(USE_TAGS, "true");

ClientOptInput input = new ClientOptInput();
input.openAPI(openAPI);
input.config(codegen);

DefaultGenerator generator = new DefaultGenerator();
generator.setGeneratorPropertyDefault(CodegenConstants.SKIP_FORM_MODEL, "false");
generator.setGenerateMetadata(false);

Map<String, File> files = generator.opts(input).generate().stream()
.collect(Collectors.toMap(this::getUniqueName, Function.identity()));

JavaFileAssert.assertThat(files.get("DefaultApi.java"))
.fileContains("import org.openapitools.model.MultiArticleImporter;")
.fileContains("import org.openapitools.model.CreateMultiArticleImporterRequest;");

Assert.assertNotNull(files.get("MultiArticleImporter.java"));
Assert.assertNotNull(files.get("CreateMultiArticleImporterRequest.java"));
}

@Test
public void testMultipleRequestBodyContentTypesWithSharedModel_issue24727() throws Exception {
Map<String, Object> additionalProperties = new HashMap<>();
additionalProperties.put(INTERFACE_ONLY, "true");
additionalProperties.put(USE_TAGS, "true");

Map<String, File> files = generateFromContract(
"src/test/resources/3_0/issue_24727_shared.yaml", SPRING_BOOT,
additionalProperties);

JavaFileAssert.assertThat(files.get("DefaultApi.java"))
.fileContains("import org.openapitools.model.ArticlePayload;");

Assert.assertNotNull(files.get("ArticlePayload.java"));
}
}
39 changes: 39 additions & 0 deletions modules/openapi-generator/src/test/resources/3_0/issue_24727.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
openapi: 3.0.3
info:
title: Multiple request-body content types repro
version: 1.0.0
paths:
/api/multi_article_importer:
post:
operationId: createMultiArticleImporter
summary: Create a multi article importer
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/MultiArticleImporter'
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
responses:
'201':
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/MultiArticleImporter'
components:
schemas:
MultiArticleImporter:
type: object
properties:
id:
type: integer
format: int64
name:
type: string
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
openapi: 3.0.3
info:
title: Shared form model repro
version: 1.0.0
paths:
/api/upload:
post:
operationId: uploadArticle
summary: Upload an article
requestBody:
required: true
content:
multipart/form-data:
schema:
$ref: '#/components/schemas/ArticlePayload'
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/ArticlePayload'
components:
schemas:
ArticlePayload:
type: object
properties:
id:
type: integer
format: int64
title:
type: string
Loading