Skip to content

Commit

Permalink
[BUG][KOTLIN] Sanitize names of the adapter variables in anyOf and on…
Browse files Browse the repository at this point in the history
…eOf 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)
  • Loading branch information
CaptainAye authored Oct 29, 2024
1 parent 48e8375 commit 32bf99a
Show file tree
Hide file tree
Showing 10 changed files with 551 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import java.io.IOException
{{#anyOf}}
{{^isArray}}
{{^vendorExtensions.x-duplicated-data-type}}
val adapter{{{dataType}}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
val adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
{{/vendorExtensions.x-duplicated-data-type}}
{{/isArray}}
{{#isArray}}
Expand Down Expand Up @@ -122,7 +122,7 @@ import java.io.IOException
return
{{/isPrimitiveType}}
{{^isPrimitiveType}}
val element = adapter{{{dataType}}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
val element = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
elementAdapter.write(out, element)
return
{{/isPrimitiveType}}
Expand Down Expand Up @@ -185,7 +185,7 @@ import java.io.IOException
for(element in jsonElement.getAsJsonArray()) {
{{#items}}
{{#isNumber}}
require(jsonElement.getAsJsonPrimitive().isNumber) {
require(element.getAsJsonPrimitive().isNumber) {
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
}
{{/isNumber}}
Expand Down Expand Up @@ -238,4 +238,99 @@ import java.io.IOException
}.nullSafe() as TypeAdapter<T>
}
}

companion object {
/**
* Validates the JSON Element and throws an exception if issues found
*
* @param jsonElement JSON Element
* @throws IOException if the JSON Element is invalid with respect to {{classname}}
*/
@Throws(IOException::class)
fun validateJsonElement(jsonElement: JsonElement?) {
requireNotNull(jsonElement) {
"Provided json element must not be null"
}
var match = 0
val errorMessages = ArrayList<String>()
{{#composedSchemas}}
{{#anyOf}}
{{^vendorExtensions.x-duplicated-data-type}}
{{^hasVars}}
// validate the json string with {{{dataType}}}
try {
// validate the JSON object to see if any exception is thrown
{{^isArray}}
{{#isNumber}}
require(jsonElement.getAsJsonPrimitive().isNumber()) {
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
}
{{/isNumber}}
{{^isNumber}}
{{#isPrimitiveType}}
require(jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) {
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())
}
{{/isPrimitiveType}}
{{/isNumber}}
{{^isNumber}}
{{^isPrimitiveType}}
{{{dataType}}}.validateJsonElement(jsonElement)
{{/isPrimitiveType}}
{{/isNumber}}
{{/isArray}}
{{#isArray}}
require(jsonElement.isJsonArray) {
String.format("Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())
}

// validate array items
for(element in jsonElement.getAsJsonArray()) {
{{#items}}
{{#isNumber}}
require(element.getAsJsonPrimitive().isNumber) {
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
}
{{/isNumber}}
{{^isNumber}}
{{#isPrimitiveType}}
require(element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}) {
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())
}
{{/isPrimitiveType}}
{{/isNumber}}
{{^isNumber}}
{{^isPrimitiveType}}
{{{dataType}}}.validateJsonElement(element)
{{/isPrimitiveType}}
{{/isNumber}}
{{/items}}
}
{{/isArray}}
match++
} catch (e: Exception) {
// Validation failed, continue
errorMessages.add(String.format("Validation for {{{dataType}}} failed with `%s`.", e.message))
}
{{/hasVars}}
{{#hasVars}}
// validate json string for {{{.}}}
try {
// validate the JSON object to see if any exception is thrown
{{.}}.validateJsonElement(jsonElement)
match++
} catch (e: Exception) {
// validation failed, continue
errorMessages.add(String.format("Validation for {{{.}}} failed with `%s`.", e.message))
}
{{/hasVars}}
{{/vendorExtensions.x-duplicated-data-type}}
{{/anyOf}}
{{/composedSchemas}}

if (match != 1) {
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()))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ import java.io.IOException
{{#oneOf}}
{{^isArray}}
{{^vendorExtensions.x-duplicated-data-type}}
val adapter{{{dataType}}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
val adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}} = gson.getDelegateAdapter(this, TypeToken.get({{{dataType}}}::class.java))
{{/vendorExtensions.x-duplicated-data-type}}
{{/isArray}}
{{#isArray}}
Expand Down Expand Up @@ -122,7 +122,7 @@ import java.io.IOException
return
{{/isPrimitiveType}}
{{^isPrimitiveType}}
val element = adapter{{{dataType}}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
val element = adapter{{#sanitizeGeneric}}{{{dataType}}}{{/sanitizeGeneric}}.toJsonTree(value.actualInstance as {{{dataType}}}?)
elementAdapter.write(out, element)
return
{{/isPrimitiveType}}
Expand Down Expand Up @@ -179,7 +179,7 @@ import java.io.IOException
for(element in jsonElement.getAsJsonArray()) {
{{#items}}
{{#isNumber}}
require(jsonElement.getAsJsonPrimitive().isNumber) {
require(element.getAsJsonPrimitive().isNumber) {
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
}
{{/isNumber}}
Expand Down Expand Up @@ -236,4 +236,99 @@ import java.io.IOException
}.nullSafe() as TypeAdapter<T>
}
}

companion object {
/**
* Validates the JSON Element and throws an exception if issues found
*
* @param jsonElement JSON Element
* @throws IOException if the JSON Element is invalid with respect to {{classname}}
*/
@Throws(IOException::class)
fun validateJsonElement(jsonElement: JsonElement?) {
requireNotNull(jsonElement) {
"Provided json element must not be null"
}
var match = 0
val errorMessages = ArrayList<String>()
{{#composedSchemas}}
{{#oneOf}}
{{^vendorExtensions.x-duplicated-data-type}}
{{^hasVars}}
// validate the json string with {{{dataType}}}
try {
// validate the JSON object to see if any exception is thrown
{{^isArray}}
{{#isNumber}}
require(jsonElement.getAsJsonPrimitive().isNumber()) {
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
}
{{/isNumber}}
{{^isNumber}}
{{#isPrimitiveType}}
require(jsonElement.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}()) {
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())
}
{{/isPrimitiveType}}
{{/isNumber}}
{{^isNumber}}
{{^isPrimitiveType}}
{{{dataType}}}.validateJsonElement(jsonElement)
{{/isPrimitiveType}}
{{/isNumber}}
{{/isArray}}
{{#isArray}}
require(jsonElement.isJsonArray) {
String.format("Expected json element to be a array type in the JSON string but got `%s`", jsonElement.toString())
}

// validate array items
for(element in jsonElement.getAsJsonArray()) {
{{#items}}
{{#isNumber}}
require(jsonElement.getAsJsonPrimitive().isNumber) {
String.format("Expected json element to be of type Number in the JSON string but got `%s`", jsonElement.toString())
}
{{/isNumber}}
{{^isNumber}}
{{#isPrimitiveType}}
require(element.getAsJsonPrimitive().is{{#isBoolean}}Boolean{{/isBoolean}}{{#isString}}String{{/isString}}{{^isString}}{{^isBoolean}}Number{{/isBoolean}}{{/isString}}) {
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())
}
{{/isPrimitiveType}}
{{/isNumber}}
{{^isNumber}}
{{^isPrimitiveType}}
{{{dataType}}}.validateJsonElement(element)
{{/isPrimitiveType}}
{{/isNumber}}
{{/items}}
}
{{/isArray}}
match++
} catch (e: Exception) {
// Validation failed, continue
errorMessages.add(String.format("Validation for {{{dataType}}} failed with `%s`.", e.message))
}
{{/hasVars}}
{{#hasVars}}
// validate json string for {{{.}}}
try {
// validate the JSON object to see if any exception is thrown
{{.}}.validateJsonElement(jsonElement)
match++
} catch (e: Exception) {
// validation failed, continue
errorMessages.add(String.format("Validation for {{{.}}} failed with `%s`.", e.message))
}
{{/hasVars}}
{{/vendorExtensions.x-duplicated-data-type}}
{{/oneOf}}
{{/composedSchemas}}

if (match != 1) {
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()))
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.openapitools.codegen.kotlin;

import lombok.Getter;
import org.jetbrains.kotlin.com.intellij.openapi.util.text.Strings;

@Getter
enum ClientLibrary {
JVM_KTOR("main/kotlin"),
JVM_OKHTTP4("main/kotlin"),
JVM_SPRING_WEBCLIENT("main/kotlin"),
JVM_SPRING_RESTCLIENT("main/kotlin"),
JVM_RETROFIT2("main/kotlin"),
MULTIPLATFORM("commonMain/kotlin"),
JVM_VOLLEY("gson", "main/java"),
JVM_VERTX("main/kotlin");
private final String serializationLibrary;
private final String libraryName;
private final String sourceRoot;

ClientLibrary(String serializationLibrary, String sourceRoot) {
this.serializationLibrary = serializationLibrary;
this.sourceRoot = sourceRoot;
this.libraryName = Strings.toLowerCase(this.name()).replace("_", "-");
}

ClientLibrary(String sourceRoot) {
this("jackson", sourceRoot);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

public class KotlinClientCodegenApiTest {

@DataProvider(name = "pathResponses")
@DataProvider(name = "clientLibraries")
public Object[][] pathResponses() {
return new Object[][]{
{ClientLibrary.JVM_KTOR},
Expand All @@ -36,7 +36,7 @@ public Object[][] pathResponses() {
};
}

@Test(dataProvider = "pathResponses")
@Test(dataProvider = "clientLibraries")
void testPathVariableIsNotEscaped_19930(ClientLibrary library) throws IOException {

OpenAPI openAPI = new OpenAPIParser()
Expand Down Expand Up @@ -76,29 +76,4 @@ private KotlinClientCodegen createCodegen(ClientLibrary library) throws IOExcept
codegen.additionalProperties().put(KotlinClientCodegen.DATE_LIBRARY, "kotlinx-datetime");
return codegen;
}

@Getter
private enum ClientLibrary {
JVM_KTOR("main/kotlin"),
JVM_OKHTTP4("main/kotlin"),
JVM_SPRING_WEBCLIENT("main/kotlin"),
JVM_SPRING_RESTCLIENT("main/kotlin"),
JVM_RETROFIT2("main/kotlin"),
MULTIPLATFORM("commonMain/kotlin"),
JVM_VOLLEY("gson", "main/java"),
JVM_VERTX("main/kotlin");
private final String serializationLibrary;
private final String libraryName;
private final String sourceRoot;

ClientLibrary(String serializationLibrary, String sourceRoot) {
this.serializationLibrary = serializationLibrary;
this.sourceRoot = sourceRoot;
this.libraryName = Strings.toLowerCase(this.name()).replace("_", "-");
}

ClientLibrary(String sourceRoot) {
this("jackson", sourceRoot);
}
}
}
Loading

0 comments on commit 32bf99a

Please sign in to comment.