diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index fef3a3824cb7..f29972e87a72 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -1002,7 +1002,7 @@ public String toModelName(final String name) { // camelize the model name // phone_number => PhoneNumber - final String camelizedName = camelize(nameWithPrefixSuffix); + String camelizedName = camelize(nameWithPrefixSuffix); // model name cannot use reserved keyword, e.g. return if (isReservedWord(camelizedName)) { @@ -1021,6 +1021,15 @@ public String toModelName(final String name) { return modelName; } + Map allDefinitions = ModelUtils.getSchemas(this.openAPI); + if(!camelizedName.equals(origName)) { + int count = 0; + String augmentedName = camelizedName; + while(allDefinitions.containsKey(augmentedName) || schemaKeyToModelNameCache.containsValue(augmentedName)) { + augmentedName = camelizedName + count++; + } + camelizedName = augmentedName; + } schemaKeyToModelNameCache.put(origName, camelizedName); return camelizedName; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java index d0b39ffe9a35..e3a3bba9f58b 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java @@ -172,8 +172,8 @@ public void convertVarNameWithCaml() { @Test public void convertModelName() { Assert.assertEquals(codegen.toModelName("name"), "Name"); - Assert.assertEquals(codegen.toModelName("$name"), "Name"); - Assert.assertEquals(codegen.toModelName("nam#e"), "Name"); + Assert.assertEquals(codegen.toModelName("$name"), "Name0"); + Assert.assertEquals(codegen.toModelName("nam#e"), "Name1"); Assert.assertEquals(codegen.toModelName("$another-fake?"), "AnotherFake"); Assert.assertEquals(codegen.toModelName("1a"), "Model1a"); Assert.assertEquals(codegen.toModelName("1A"), "Model1A"); @@ -181,11 +181,11 @@ public void convertModelName() { Assert.assertEquals(codegen.toModelName("aBB"), "ABB"); Assert.assertEquals(codegen.toModelName("AaBBa"), "AaBBa"); Assert.assertEquals(codegen.toModelName("A_B"), "AB"); - Assert.assertEquals(codegen.toModelName("A-B"), "AB"); + Assert.assertEquals(codegen.toModelName("A-B"), "AB0"); Assert.assertEquals(codegen.toModelName("Aa_Bb"), "AaBb"); - Assert.assertEquals(codegen.toModelName("Aa-Bb"), "AaBb"); - Assert.assertEquals(codegen.toModelName("Aa_bb"), "AaBb"); - Assert.assertEquals(codegen.toModelName("Aa-bb"), "AaBb"); + Assert.assertEquals(codegen.toModelName("Aa-Bb"), "AaBb0"); + Assert.assertEquals(codegen.toModelName("Aa_bb"), "AaBb1"); + Assert.assertEquals(codegen.toModelName("Aa-bb"), "AaBb2"); } @Test diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java index 0b84b410fae0..f9f8dcfbd057 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java @@ -3833,6 +3833,57 @@ public void queryParameterJsonSerialization(String library) { ); } + @Test(description = "Issue #20889") + public void givenInlineSchemaConvertedNameClashesWithTopLevelSchemaNameWhenGenerateThenFilesDoNotOverwriteEachOther() throws Exception { + final Path output = newTempFolder(); + final CodegenConfigurator configurator = new CodegenConfigurator() + .setGeneratorName(JAVA_GENERATOR) + .setInputSpec("src/test/resources/bugs/issue_20889.json") + .setValidateSpec(false) + .setOutputDir(output.toString().replace("\\", "/")); + ; + DefaultGenerator generator = new DefaultGenerator(); + Map files = generator.opts(configurator.toClientOptInput()).generate().stream() + .collect(Collectors.toMap(File::getName, Function.identity())); + + JavaFileAssert.assertThat(files.get("OrgAttributes.java")) + .assertProperty("createdAt") + .withType("OffsetDateTime"); + + JavaFileAssert.assertThat(files.get("OrgAttributes0.java")) + .hasNoProperty("createdAt"); + + JavaFileAssert.assertThat(files.get("Org.java")) + .assertProperty("attributes") + .withType("OrgAttributes0") + .toType() + .assertProperty("branch") + .withType("OrgBranch0"); + + JavaFileAssert.assertThat(files.get("OrgBranch.java")) + .assertProperty("attributes") + .withType("OrgBranchAttributes0"); + + JavaFileAssert.assertThat(files.get("OrgBranch0.java")) + .assertProperty("attributes") + .withType("OrgBranchAttributes1"); + + JavaFileAssert.assertThat(files.get("OrgBranchAttributes1.java")) + .assertProperty("basicDescription") + .withType("String"); + + JavaFileAssert.assertThat(files.get("OrgBranchAttributes0.java")) + .assertProperty("uuid") + .withType("UUID"); + + JavaFileAssert.assertThat(files.get("OrgBranchAttributes.java")) + .assertProperty("detailedDescription") + .withType("String"); + + assertTrue(files.containsKey("OrgBranchAttributes0Test.java")); + assertTrue(files.containsKey("OrgBranchAttributes1Test.java")); + } + @DataProvider(name = "springClients") public static Object[] springClients() { return new Object[]{RESTCLIENT, WEBCLIENT}; diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java index d6c9d229ea2f..f67bbbb142c6 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java @@ -201,4 +201,16 @@ public JavaFileAssert fileContainsPattern(final String pattern) { return this; } + + public JavaFileAssert hasNoProperty(final String propertyName) { + Optional fieldOptional = actual.getType(0).getMembers().stream() + .filter(FieldDeclaration.class::isInstance) + .map(FieldDeclaration.class::cast) + .filter(field -> field.getVariables().getFirst().map(var -> var.getNameAsString().equals(propertyName)).orElse(Boolean.FALSE)) + .findFirst(); + Assertions.assertThat(fieldOptional) + .withFailMessage("Should not have property %s", propertyName) + .isNotPresent(); + return this; + } } diff --git a/modules/openapi-generator/src/test/resources/bugs/issue_20889.json b/modules/openapi-generator/src/test/resources/bugs/issue_20889.json new file mode 100644 index 000000000000..3d01d487b995 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/bugs/issue_20889.json @@ -0,0 +1,220 @@ +{ + "components": { + "schemas": { + "Org": { + "additionalProperties": false, + "properties": { + "attributes": { + "additionalProperties": false, + "properties": { + "group_id": { + "description": "The ID of a Group.", + "example": "59d6d97e-3106-4ebb-b608-352fad9c5b34", + "format": "uuid", + "type": "string" + }, + "is_personal": { + "description": "Whether this organization belongs to an individual, rather than a Group.", + "example": true, + "type": "boolean" + }, + "name": { + "description": "Friendly name of the organization.", + "example": "My Org", + "type": "string" + }, + "slug": { + "description": "Unique URL sanitized name of the organization for accessing it in Snyk.", + "example": "my-org", + "type": "string" + } + }, + "required": [ + "name", + "slug", + "is_personal" + ], + "type": "object" + }, + "id": { + "description": "The Snyk ID corresponding to this org", + "example": "59d6d97e-3106-4ebb-b608-352fad9c5b34", + "format": "uuid", + "type": "string" + }, + "type": { + "description": "Content type.", + "example": "org", + "type": "string" + }, + "branch": { + "additionalProperties": false, + "type": "object", + "properties": { + "attributes": { + "type": "object", + "properties": { + "basicDescription": { + "type": "string" + } + } + } + } + } + }, + "required": [ + "type", + "id" + ], + "type": "object" + }, + "Org20230529": { + "additionalProperties": false, + "properties": { + "attributes": { + "$ref": "#/components/schemas/OrgAttributes20230529" + }, + "id": { + "description": "The Snyk ID of the organization.", + "example": "59d6d97e-3106-4ebb-b608-352fad9c5b34", + "format": "uuid", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/Types" + } + }, + "required": [ + "type", + "id", + "attributes" + ], + "type": "object" + }, + "OrgAttributes": { + "additionalProperties": false, + "properties": { + "access_requests_enabled": { + "description": "Whether the organization permits access requests from users who are not members of the organization.", + "example": false, + "type": "boolean" + }, + "created_at": { + "description": "The time the organization was created.", + "example": "2022-03-16T00:00:00Z", + "format": "date-time", + "type": "string" + }, + "group_id": { + "description": "The Snyk ID of the group to which the organization belongs.", + "example": "59d6d97e-3106-4ebb-b608-352fad9c5b34", + "format": "uuid", + "type": "string" + }, + "is_personal": { + "description": "Whether the organization is independent (that is, not part of a group).", + "example": true, + "type": "boolean" + }, + "name": { + "description": "The display name of the organization.", + "example": "My Org", + "type": "string" + }, + "slug": { + "description": "The canonical (unique and URL-friendly) name of the organization.", + "example": "my-org", + "type": "string" + }, + "updated_at": { + "description": "The time the organization was last modified.", + "example": "2022-03-16T00:00:00Z", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "name", + "slug", + "is_personal" + ], + "type": "object" + }, + "OrgAttributes20230529": { + "additionalProperties": false, + "properties": { + "created_at": { + "description": "The time the organization was created.", + "example": "2022-03-16T00:00:00Z", + "format": "date-time", + "type": "string" + }, + "group_id": { + "description": "The Snyk ID of the group to which the organization belongs.", + "example": "59d6d97e-3106-4ebb-b608-352fad9c5b34", + "format": "uuid", + "type": "string" + }, + "is_personal": { + "description": "Whether the organization is independent (that is, not part of a group).", + "example": true, + "type": "boolean" + }, + "name": { + "description": "The display name of the organization.", + "example": "My Org", + "type": "string" + }, + "slug": { + "description": "The canonical (unique and URL-friendly) name of the organization.", + "example": "my-org", + "type": "string" + }, + "updated_at": { + "description": "The time the organization was last modified.", + "example": "2022-03-16T00:00:00Z", + "format": "date-time", + "type": "string" + } + }, + "required": [ + "name", + "slug", + "is_personal" + ], + "type": "object" + }, + "OrgBranch": { + "additionalProperties": false, + "type": "object", + "properties": { + "attributes": { + "additionalProperties": false, + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + } + } + } + } + }, + "OrgBranchAttributes": { + "additionalProperties": false, + "type": "object", + "properties": { + "detailedDescription": { + "type": "string" + } + } + } + } + }, + "info": { + "title": "Snyk API", + "version": "REST" + }, + "openapi": "3.0.3" + +} \ No newline at end of file