Skip to content

Commit 32bf99a

Browse files
authored
[BUG][KOTLIN] Sanitize names of the adapter variables in anyOf and oneOf model template to avoid compilation errors (OpenAPITools#19981)
* [kotlin] Sanitize one_of and any_of model variable names to avoid compilation errors (OpenAPITools#19942) * [kotlin] add missing validateJsonElement method to oneOf and anyOf model templates (OpenAPITools#19942)
1 parent 48e8375 commit 32bf99a

File tree

10 files changed

+551
-33
lines changed

10 files changed

+551
-33
lines changed

modules/openapi-generator/src/main/resources/kotlin-client/anyof_class.mustache

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import java.io.IOException
8383
{{#anyOf}}
8484
{{^isArray}}
8585
{{^vendorExtensions.x-duplicated-data-type}}
86-
val adapter{{{dataType}}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
86+
val adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
8787
{{/vendorExtensions.x-duplicated-data-type}}
8888
{{/isArray}}
8989
{{#isArray}}
@@ -122,7 +122,7 @@ import java.io.IOException
122122
return
123123
{{/isPrimitiveType}}
124124
{{^isPrimitiveType}}
125-
val element = adapter{{{dataType}}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
125+
val element = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
126126
elementAdapter.write(out, element)
127127
return
128128
{{/isPrimitiveType}}
@@ -185,7 +185,7 @@ import java.io.IOException
185185
for(element in jsonElement.getAsJsonArray()) {
186186
{{#items}}
187187
{{#isNumber}}
188-
require(jsonElement.getAsJsonPrimitive().isNumber) {
188+
require(element.getAsJsonPrimitive().isNumber) {
189189
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
190190
}
191191
{{/isNumber}}
@@ -238,4 +238,99 @@ import java.io.IOException
238238
}.nullSafe() as TypeAdapter<T>
239239
}
240240
}
241+
242+
companion object {
243+
/**
244+
* Validates the JSON Element and throws an exception if issues found
245+
*
246+
* @param jsonElement JSON Element
247+
* @throws IOException if the JSON Element is invalid with respect to {{classname}}
248+
*/
249+
@Throws(IOException::class)
250+
fun validateJsonElement(jsonElement: JsonElement?) {
251+
requireNotNull(jsonElement) {
252+
"Provided json element must not be null"
253+
}
254+
var match = 0
255+
val errorMessages = ArrayList<String>()
256+
{{#composedSchemas}}
257+
{{#anyOf}}
258+
{{^vendorExtensions.x-duplicated-data-type}}
259+
{{^hasVars}}
260+
// validate the json string with {{{dataType}}}
261+
try {
262+
// validate the JSON object to see if any exception is thrown
263+
{{^isArray}}
264+
{{#isNumber}}
265+
require(jsonElement.getAsJsonPrimitive().isNumber()) {
266+
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
267+
}
268+
{{/isNumber}}
269+
{{^isNumber}}
270+
{{#isPrimitiveType}}
271+
require(jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) {
272+
String.format("Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())
273+
}
274+
{{/isPrimitiveType}}
275+
{{/isNumber}}
276+
{{^isNumber}}
277+
{{^isPrimitiveType}}
278+
{{{dataType}}}.validateJsonElement(jsonElement)
279+
{{/isPrimitiveType}}
280+
{{/isNumber}}
281+
{{/isArray}}
282+
{{#isArray}}
283+
require(jsonElement.isJsonArray) {
284+
String.format("Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())
285+
}
286+
287+
// validate array items
288+
for(element in jsonElement.getAsJsonArray()) {
289+
{{#items}}
290+
{{#isNumber}}
291+
require(element.getAsJsonPrimitive().isNumber) {
292+
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
293+
}
294+
{{/isNumber}}
295+
{{^isNumber}}
296+
{{#isPrimitiveType}}
297+
require(element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}) {
298+
String.format("Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())
299+
}
300+
{{/isPrimitiveType}}
301+
{{/isNumber}}
302+
{{^isNumber}}
303+
{{^isPrimitiveType}}
304+
{{{dataType}}}.validateJsonElement(element)
305+
{{/isPrimitiveType}}
306+
{{/isNumber}}
307+
{{/items}}
308+
}
309+
{{/isArray}}
310+
match++
311+
} catch (e: Exception) {
312+
// Validation failed, continue
313+
errorMessages.add(String.format("Validation for {{{dataType}}} failed with `%s`.", e.message))
314+
}
315+
{{/hasVars}}
316+
{{#hasVars}}
317+
// validate json string for {{{.}}}
318+
try {
319+
// validate the JSON object to see if any exception is thrown
320+
{{.}}.validateJsonElement(jsonElement)
321+
match++
322+
} catch (e: Exception) {
323+
// validation failed, continue
324+
errorMessages.add(String.format("Validation for {{{.}}} failed with `%s`.", e.message))
325+
}
326+
{{/hasVars}}
327+
{{/vendorExtensions.x-duplicated-data-type}}
328+
{{/anyOf}}
329+
{{/composedSchemas}}
330+
331+
if (match != 1) {
332+
throw IOException(String.format("Failed validation for {{classname}}: %d classes match result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", match, errorMessages, jsonElement.toString()))
333+
}
334+
}
335+
}
241336
}

modules/openapi-generator/src/main/resources/kotlin-client/oneof_class.mustache

Lines changed: 98 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ import java.io.IOException
8383
{{#oneOf}}
8484
{{^isArray}}
8585
{{^vendorExtensions.x-duplicated-data-type}}
86-
val adapter{{{dataType}}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
86+
val adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
8787
{{/vendorExtensions.x-duplicated-data-type}}
8888
{{/isArray}}
8989
{{#isArray}}
@@ -122,7 +122,7 @@ import java.io.IOException
122122
return
123123
{{/isPrimitiveType}}
124124
{{^isPrimitiveType}}
125-
val element = adapter{{{dataType}}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
125+
val element = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
126126
elementAdapter.write(out, element)
127127
return
128128
{{/isPrimitiveType}}
@@ -179,7 +179,7 @@ import java.io.IOException
179179
for(element in jsonElement.getAsJsonArray()) {
180180
{{#items}}
181181
{{#isNumber}}
182-
require(jsonElement.getAsJsonPrimitive().isNumber) {
182+
require(element.getAsJsonPrimitive().isNumber) {
183183
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
184184
}
185185
{{/isNumber}}
@@ -236,4 +236,99 @@ import java.io.IOException
236236
}.nullSafe() as TypeAdapter<T>
237237
}
238238
}
239+
240+
companion object {
241+
/**
242+
* Validates the JSON Element and throws an exception if issues found
243+
*
244+
* @param jsonElement JSON Element
245+
* @throws IOException if the JSON Element is invalid with respect to {{classname}}
246+
*/
247+
@Throws(IOException::class)
248+
fun validateJsonElement(jsonElement: JsonElement?) {
249+
requireNotNull(jsonElement) {
250+
"Provided json element must not be null"
251+
}
252+
var match = 0
253+
val errorMessages = ArrayList<String>()
254+
{{#composedSchemas}}
255+
{{#oneOf}}
256+
{{^vendorExtensions.x-duplicated-data-type}}
257+
{{^hasVars}}
258+
// validate the json string with {{{dataType}}}
259+
try {
260+
// validate the JSON object to see if any exception is thrown
261+
{{^isArray}}
262+
{{#isNumber}}
263+
require(jsonElement.getAsJsonPrimitive().isNumber()) {
264+
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
265+
}
266+
{{/isNumber}}
267+
{{^isNumber}}
268+
{{#isPrimitiveType}}
269+
require(jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) {
270+
String.format("Expected json element to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())
271+
}
272+
{{/isPrimitiveType}}
273+
{{/isNumber}}
274+
{{^isNumber}}
275+
{{^isPrimitiveType}}
276+
{{{dataType}}}.validateJsonElement(jsonElement)
277+
{{/isPrimitiveType}}
278+
{{/isNumber}}
279+
{{/isArray}}
280+
{{#isArray}}
281+
require(jsonElement.isJsonArray) {
282+
String.format("Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())
283+
}
284+
285+
// validate array items
286+
for(element in jsonElement.getAsJsonArray()) {
287+
{{#items}}
288+
{{#isNumber}}
289+
require(jsonElement.getAsJsonPrimitive().isNumber) {
290+
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
291+
}
292+
{{/isNumber}}
293+
{{^isNumber}}
294+
{{#isPrimitiveType}}
295+
require(element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}) {
296+
String.format("Expected array items to be of type {{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}} in the JSON string but got `%s`", jsonElement.toString())
297+
}
298+
{{/isPrimitiveType}}
299+
{{/isNumber}}
300+
{{^isNumber}}
301+
{{^isPrimitiveType}}
302+
{{{dataType}}}.validateJsonElement(element)
303+
{{/isPrimitiveType}}
304+
{{/isNumber}}
305+
{{/items}}
306+
}
307+
{{/isArray}}
308+
match++
309+
} catch (e: Exception) {
310+
// Validation failed, continue
311+
errorMessages.add(String.format("Validation for {{{dataType}}} failed with `%s`.", e.message))
312+
}
313+
{{/hasVars}}
314+
{{#hasVars}}
315+
// validate json string for {{{.}}}
316+
try {
317+
// validate the JSON object to see if any exception is thrown
318+
{{.}}.validateJsonElement(jsonElement)
319+
match++
320+
} catch (e: Exception) {
321+
// validation failed, continue
322+
errorMessages.add(String.format("Validation for {{{.}}} failed with `%s`.", e.message))
323+
}
324+
{{/hasVars}}
325+
{{/vendorExtensions.x-duplicated-data-type}}
326+
{{/oneOf}}
327+
{{/composedSchemas}}
328+
329+
if (match != 1) {
330+
throw IOException(String.format("Failed validation for {{classname}}: %d classes match result, expected 1. Detailed failure message for oneOf schemas: %s. JSON: %s", match, errorMessages, jsonElement.toString()))
331+
}
332+
}
333+
}
239334
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.openapitools.codegen.kotlin;
2+
3+
import lombok.Getter;
4+
import org.jetbrains.kotlin.com.intellij.openapi.util.text.Strings;
5+
6+
@Getter
7+
enum ClientLibrary {
8+
JVM_KTOR("main/kotlin"),
9+
JVM_OKHTTP4("main/kotlin"),
10+
JVM_SPRING_WEBCLIENT("main/kotlin"),
11+
JVM_SPRING_RESTCLIENT("main/kotlin"),
12+
JVM_RETROFIT2("main/kotlin"),
13+
MULTIPLATFORM("commonMain/kotlin"),
14+
JVM_VOLLEY("gson", "main/java"),
15+
JVM_VERTX("main/kotlin");
16+
private final String serializationLibrary;
17+
private final String libraryName;
18+
private final String sourceRoot;
19+
20+
ClientLibrary(String serializationLibrary, String sourceRoot) {
21+
this.serializationLibrary = serializationLibrary;
22+
this.sourceRoot = sourceRoot;
23+
this.libraryName = Strings.toLowerCase(this.name()).replace("_", "-");
24+
}
25+
26+
ClientLibrary(String sourceRoot) {
27+
this("jackson", sourceRoot);
28+
}
29+
}

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

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
public class KotlinClientCodegenApiTest {
2424

25-
@DataProvider(name = "pathResponses")
25+
@DataProvider(name = "clientLibraries")
2626
public Object[][] pathResponses() {
2727
return new Object[][]{
2828
{ClientLibrary.JVM_KTOR},
@@ -36,7 +36,7 @@ public Object[][] pathResponses() {
3636
};
3737
}
3838

39-
@Test(dataProvider = "pathResponses")
39+
@Test(dataProvider = "clientLibraries")
4040
void testPathVariableIsNotEscaped_19930(ClientLibrary library) throws IOException {
4141

4242
OpenAPI openAPI = new OpenAPIParser()
@@ -76,29 +76,4 @@ private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOExcept
7676
codegen.additionalProperties().put(KotlinClientCodegen.DATE_LIBRARY, "kotlinx-datetime");
7777
return codegen;
7878
}
79-
80-
@Getter
81-
private enum ClientLibrary {
82-
JVM_KTOR("main/kotlin"),
83-
JVM_OKHTTP4("main/kotlin"),
84-
JVM_SPRING_WEBCLIENT("main/kotlin"),
85-
JVM_SPRING_RESTCLIENT("main/kotlin"),
86-
JVM_RETROFIT2("main/kotlin"),
87-
MULTIPLATFORM("commonMain/kotlin"),
88-
JVM_VOLLEY("gson", "main/java"),
89-
JVM_VERTX("main/kotlin");
90-
private final String serializationLibrary;
91-
private final String libraryName;
92-
private final String sourceRoot;
93-
94-
ClientLibrary(String serializationLibrary, String sourceRoot) {
95-
this.serializationLibrary = serializationLibrary;
96-
this.sourceRoot = sourceRoot;
97-
this.libraryName = Strings.toLowerCase(this.name()).replace("_", "-");
98-
}
99-
100-
ClientLibrary(String sourceRoot) {
101-
this("jackson", sourceRoot);
102-
}
103-
}
10479
}

0 commit comments

Comments
 (0)