Skip to content

Commit f2cf306

Browse files
committed
[Java] Models derived from inline schemas no longer overwrite top-level schemas with the same resolved name (Issue #20889)
1 parent afedd3f commit f2cf306

5 files changed

Lines changed: 299 additions & 7 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ public String toModelName(final String name) {
10021002

10031003
// camelize the model name
10041004
// phone_number => PhoneNumber
1005-
final String camelizedName = camelize(nameWithPrefixSuffix);
1005+
String camelizedName = camelize(nameWithPrefixSuffix);
10061006

10071007
// model name cannot use reserved keyword, e.g. return
10081008
if (isReservedWord(camelizedName)) {
@@ -1021,6 +1021,15 @@ public String toModelName(final String name) {
10211021
return modelName;
10221022
}
10231023

1024+
Map<String, Schema> allDefinitions = ModelUtils.getSchemas(this.openAPI);
1025+
if(!camelizedName.equals(origName)) {
1026+
int count = 0;
1027+
String augmentedName = camelizedName;
1028+
while(allDefinitions.containsKey(augmentedName) || schemaKeyToModelNameCache.containsValue(augmentedName)) {
1029+
augmentedName = camelizedName + count++;
1030+
}
1031+
camelizedName = augmentedName;
1032+
}
10241033
schemaKeyToModelNameCache.put(origName, camelizedName);
10251034

10261035
return camelizedName;

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/AbstractJavaCodegenTest.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,20 +172,20 @@ public void convertVarNameWithCaml() {
172172
@Test
173173
public void convertModelName() {
174174
Assert.assertEquals(codegen.toModelName("name"), "Name");
175-
Assert.assertEquals(codegen.toModelName("$name"), "Name");
176-
Assert.assertEquals(codegen.toModelName("nam#e"), "Name");
175+
Assert.assertEquals(codegen.toModelName("$name"), "Name0");
176+
Assert.assertEquals(codegen.toModelName("nam#e"), "Name1");
177177
Assert.assertEquals(codegen.toModelName("$another-fake?"), "AnotherFake");
178178
Assert.assertEquals(codegen.toModelName("1a"), "Model1a");
179179
Assert.assertEquals(codegen.toModelName("1A"), "Model1A");
180180
Assert.assertEquals(codegen.toModelName("AAAb"), "AAAb");
181181
Assert.assertEquals(codegen.toModelName("aBB"), "ABB");
182182
Assert.assertEquals(codegen.toModelName("AaBBa"), "AaBBa");
183183
Assert.assertEquals(codegen.toModelName("A_B"), "AB");
184-
Assert.assertEquals(codegen.toModelName("A-B"), "AB");
184+
Assert.assertEquals(codegen.toModelName("A-B"), "AB0");
185185
Assert.assertEquals(codegen.toModelName("Aa_Bb"), "AaBb");
186-
Assert.assertEquals(codegen.toModelName("Aa-Bb"), "AaBb");
187-
Assert.assertEquals(codegen.toModelName("Aa_bb"), "AaBb");
188-
Assert.assertEquals(codegen.toModelName("Aa-bb"), "AaBb");
186+
Assert.assertEquals(codegen.toModelName("Aa-Bb"), "AaBb0");
187+
Assert.assertEquals(codegen.toModelName("Aa_bb"), "AaBb1");
188+
Assert.assertEquals(codegen.toModelName("Aa-bb"), "AaBb2");
189189
}
190190

191191
@Test

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/JavaClientCodegenTest.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3833,6 +3833,57 @@ public void queryParameterJsonSerialization(String library) {
38333833
);
38343834
}
38353835

3836+
@Test(description = "Issue #20889")
3837+
public void givenInlineSchemaConvertedNameClashesWithTopLevelSchemaNameWhenGenerateThenFilesDoNotOverwriteEachOther() throws Exception {
3838+
final Path output = newTempFolder();
3839+
final CodegenConfigurator configurator = new CodegenConfigurator()
3840+
.setGeneratorName(JAVA_GENERATOR)
3841+
.setInputSpec("src/test/resources/bugs/issue_20889.json")
3842+
.setValidateSpec(false)
3843+
.setOutputDir(output.toString().replace("\\", "/"));
3844+
;
3845+
DefaultGenerator generator = new DefaultGenerator();
3846+
Map<String, File> files = generator.opts(configurator.toClientOptInput()).generate().stream()
3847+
.collect(Collectors.toMap(File::getName, Function.identity()));
3848+
3849+
JavaFileAssert.assertThat(files.get("OrgAttributes.java"))
3850+
.assertProperty("createdAt")
3851+
.withType("OffsetDateTime");
3852+
3853+
JavaFileAssert.assertThat(files.get("OrgAttributes0.java"))
3854+
.hasNoProperty("createdAt");
3855+
3856+
JavaFileAssert.assertThat(files.get("Org.java"))
3857+
.assertProperty("attributes")
3858+
.withType("OrgAttributes0")
3859+
.toType()
3860+
.assertProperty("branch")
3861+
.withType("OrgBranch0");
3862+
3863+
JavaFileAssert.assertThat(files.get("OrgBranch.java"))
3864+
.assertProperty("attributes")
3865+
.withType("OrgBranchAttributes0");
3866+
3867+
JavaFileAssert.assertThat(files.get("OrgBranch0.java"))
3868+
.assertProperty("attributes")
3869+
.withType("OrgBranchAttributes1");
3870+
3871+
JavaFileAssert.assertThat(files.get("OrgBranchAttributes1.java"))
3872+
.assertProperty("basicDescription")
3873+
.withType("String");
3874+
3875+
JavaFileAssert.assertThat(files.get("OrgBranchAttributes0.java"))
3876+
.assertProperty("uuid")
3877+
.withType("UUID");
3878+
3879+
JavaFileAssert.assertThat(files.get("OrgBranchAttributes.java"))
3880+
.assertProperty("detailedDescription")
3881+
.withType("String");
3882+
3883+
assertTrue(files.containsKey("OrgBranchAttributes0Test.java"));
3884+
assertTrue(files.containsKey("OrgBranchAttributes1Test.java"));
3885+
}
3886+
38363887
@DataProvider(name = "springClients")
38373888
public static Object[] springClients() {
38383889
return new Object[]{RESTCLIENT, WEBCLIENT};

modules/openapi-generator/src/test/java/org/openapitools/codegen/java/assertions/JavaFileAssert.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,4 +201,16 @@ public JavaFileAssert fileContainsPattern(final String pattern) {
201201

202202
return this;
203203
}
204+
205+
public JavaFileAssert hasNoProperty(final String propertyName) {
206+
Optional<FieldDeclaration> fieldOptional = actual.getType(0).getMembers().stream()
207+
.filter(FieldDeclaration.class::isInstance)
208+
.map(FieldDeclaration.class::cast)
209+
.filter(field -> field.getVariables().getFirst().map(var -> var.getNameAsString().equals(propertyName)).orElse(Boolean.FALSE))
210+
.findFirst();
211+
Assertions.assertThat(fieldOptional)
212+
.withFailMessage("Should not have property %s", propertyName)
213+
.isNotPresent();
214+
return this;
215+
}
204216
}
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
{
2+
"components": {
3+
"schemas": {
4+
"Org": {
5+
"additionalProperties": false,
6+
"properties": {
7+
"attributes": {
8+
"additionalProperties": false,
9+
"properties": {
10+
"group_id": {
11+
"description": "The ID of a Group.",
12+
"example": "59d6d97e-3106-4ebb-b608-352fad9c5b34",
13+
"format": "uuid",
14+
"type": "string"
15+
},
16+
"is_personal": {
17+
"description": "Whether this organization belongs to an individual, rather than a Group.",
18+
"example": true,
19+
"type": "boolean"
20+
},
21+
"name": {
22+
"description": "Friendly name of the organization.",
23+
"example": "My Org",
24+
"type": "string"
25+
},
26+
"slug": {
27+
"description": "Unique URL sanitized name of the organization for accessing it in Snyk.",
28+
"example": "my-org",
29+
"type": "string"
30+
}
31+
},
32+
"required": [
33+
"name",
34+
"slug",
35+
"is_personal"
36+
],
37+
"type": "object"
38+
},
39+
"id": {
40+
"description": "The Snyk ID corresponding to this org",
41+
"example": "59d6d97e-3106-4ebb-b608-352fad9c5b34",
42+
"format": "uuid",
43+
"type": "string"
44+
},
45+
"type": {
46+
"description": "Content type.",
47+
"example": "org",
48+
"type": "string"
49+
},
50+
"branch": {
51+
"additionalProperties": false,
52+
"type": "object",
53+
"properties": {
54+
"attributes": {
55+
"type": "object",
56+
"properties": {
57+
"basicDescription": {
58+
"type": "string"
59+
}
60+
}
61+
}
62+
}
63+
}
64+
},
65+
"required": [
66+
"type",
67+
"id"
68+
],
69+
"type": "object"
70+
},
71+
"Org20230529": {
72+
"additionalProperties": false,
73+
"properties": {
74+
"attributes": {
75+
"$ref": "#/components/schemas/OrgAttributes20230529"
76+
},
77+
"id": {
78+
"description": "The Snyk ID of the organization.",
79+
"example": "59d6d97e-3106-4ebb-b608-352fad9c5b34",
80+
"format": "uuid",
81+
"type": "string"
82+
},
83+
"type": {
84+
"$ref": "#/components/schemas/Types"
85+
}
86+
},
87+
"required": [
88+
"type",
89+
"id",
90+
"attributes"
91+
],
92+
"type": "object"
93+
},
94+
"OrgAttributes": {
95+
"additionalProperties": false,
96+
"properties": {
97+
"access_requests_enabled": {
98+
"description": "Whether the organization permits access requests from users who are not members of the organization.",
99+
"example": false,
100+
"type": "boolean"
101+
},
102+
"created_at": {
103+
"description": "The time the organization was created.",
104+
"example": "2022-03-16T00:00:00Z",
105+
"format": "date-time",
106+
"type": "string"
107+
},
108+
"group_id": {
109+
"description": "The Snyk ID of the group to which the organization belongs.",
110+
"example": "59d6d97e-3106-4ebb-b608-352fad9c5b34",
111+
"format": "uuid",
112+
"type": "string"
113+
},
114+
"is_personal": {
115+
"description": "Whether the organization is independent (that is, not part of a group).",
116+
"example": true,
117+
"type": "boolean"
118+
},
119+
"name": {
120+
"description": "The display name of the organization.",
121+
"example": "My Org",
122+
"type": "string"
123+
},
124+
"slug": {
125+
"description": "The canonical (unique and URL-friendly) name of the organization.",
126+
"example": "my-org",
127+
"type": "string"
128+
},
129+
"updated_at": {
130+
"description": "The time the organization was last modified.",
131+
"example": "2022-03-16T00:00:00Z",
132+
"format": "date-time",
133+
"type": "string"
134+
}
135+
},
136+
"required": [
137+
"name",
138+
"slug",
139+
"is_personal"
140+
],
141+
"type": "object"
142+
},
143+
"OrgAttributes20230529": {
144+
"additionalProperties": false,
145+
"properties": {
146+
"created_at": {
147+
"description": "The time the organization was created.",
148+
"example": "2022-03-16T00:00:00Z",
149+
"format": "date-time",
150+
"type": "string"
151+
},
152+
"group_id": {
153+
"description": "The Snyk ID of the group to which the organization belongs.",
154+
"example": "59d6d97e-3106-4ebb-b608-352fad9c5b34",
155+
"format": "uuid",
156+
"type": "string"
157+
},
158+
"is_personal": {
159+
"description": "Whether the organization is independent (that is, not part of a group).",
160+
"example": true,
161+
"type": "boolean"
162+
},
163+
"name": {
164+
"description": "The display name of the organization.",
165+
"example": "My Org",
166+
"type": "string"
167+
},
168+
"slug": {
169+
"description": "The canonical (unique and URL-friendly) name of the organization.",
170+
"example": "my-org",
171+
"type": "string"
172+
},
173+
"updated_at": {
174+
"description": "The time the organization was last modified.",
175+
"example": "2022-03-16T00:00:00Z",
176+
"format": "date-time",
177+
"type": "string"
178+
}
179+
},
180+
"required": [
181+
"name",
182+
"slug",
183+
"is_personal"
184+
],
185+
"type": "object"
186+
},
187+
"OrgBranch": {
188+
"additionalProperties": false,
189+
"type": "object",
190+
"properties": {
191+
"attributes": {
192+
"additionalProperties": false,
193+
"type": "object",
194+
"properties": {
195+
"uuid": {
196+
"type": "string",
197+
"format": "uuid"
198+
}
199+
}
200+
}
201+
}
202+
},
203+
"OrgBranchAttributes": {
204+
"additionalProperties": false,
205+
"type": "object",
206+
"properties": {
207+
"detailedDescription": {
208+
"type": "string"
209+
}
210+
}
211+
}
212+
}
213+
},
214+
"info": {
215+
"title": "Snyk API",
216+
"version": "REST"
217+
},
218+
"openapi": "3.0.3"
219+
220+
}

0 commit comments

Comments
 (0)