OpenAPITools / OpenAPITools/openapi-generator

[BUG] [Kotlin] Missing string array check for validity

Open
#21,284 4 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Issue: Bug
Dominant language
Java
Stars
26.8k
Forks
7.7k
PR merge metrics
PR metrics pending

Description

openapi-generator version

7.13.0

OpenAPI declaration file content or url
"data": {
  "type": "array",
  "items": {
    "oneOf": [
      {
        "$ref": "#/components/schemas/LocationDataTemperature"
      },
      {
        "$ref": "#/components/schemas/LocationDataWind"
      }
    ]
  }
},

"LocationDataTemperature": {
  "type": "object",
  "properties": {
    "display": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": ["display"]
},

"LocationDataWind": {
  "type": "object",
  "properties": {
    "display": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "FF_KMH": {
            "type": "number",
            "format": "float"
          },
          "FX_KMH": {
            "type": "number",
            "format": "float"
          },
          "DD_DEG": {
            "type": "integer"
          }
        },
        "required": ["FF_KMH", "FX_KMH", "DD_DEG"]
      }
    }
  },
  "required": ["display"]
Generation Details

Gradle Task

    val meteoMapApiName = "MeteoMap"
    val meteoMapApiGenPackage = "${project.android.namespace}.${meteoMapApiName.lowercase()}"
    val generateMeteoMap by registering(GenerateTask::class) {
        generatorName.set("kotlin")
        inputSpec.set("${project.projectDir}/${meteoMapApiName.lowercase()}-api.json")
        outputDir.set("$buildDir/generated")
        packageName.set(meteoMapApiGenPackage)
        apiPackage.set("$meteoMapApiGenPackage.api")
        modelNamePrefix.set(meteoMapApiName)
        invokerPackage.set("$meteoMapApiGenPackage.invoker")
        modelPackage.set("$meteoMapApiGenPackage.model")
        configOptions.set(apiConfigOptions)
        additionalProperties.set(
            mapOf(
                "enumPropertyNaming" to "UPPERCASE",
                "useCoroutines" to "true",
                "moshiCodeGen" to "true",
                "generateOneOfAnyOfWrappers" to "true",
                "serializationLibrary" to "gson",
            ),
        )
        library.set("jvm-retrofit2")
    }
Steps to reproduce

When we receive a response from the server and parse it's body, an exception is thrown. Because too many Type Adapters match with the result.

Example of a json response:

"display": [
  {
  "FF_KMH": 4,
  "FX_KMH": 7,
  "DD_DEG": 212,
  "dateTime": "2025-05-15T00:00:00+00:00"
  }
]

This should obviousley only match to the type of LocationDataWind according to the schema provided above, but also the type of LocationDataTemperature apparantly does match the result. So I checked out the LocationDataTemperature.validateJsonElement() function.

        @Throws(IOException::class)
        fun validateJsonElement(jsonElement: JsonElement?) {
            if (jsonElement == null) {
              require(openapiRequiredFields.isEmpty()) { // has required fields but JSON element is null
                String.format("The required field(s) %s in MeteoMapLocationDataTemperature is not found in the empty JSON string", MeteoMapLocationDataTemperature.openapiRequiredFields.toString())
              }
            }

            // check to make sure all required properties/fields are present in the JSON string
            for (requiredField in openapiRequiredFields) {
              requireNotNull(jsonElement!!.getAsJsonObject()[requiredField]) {
                String.format("The required field `%s` is not found in the JSON string: %s", requiredField, jsonElement.toString())
              }
            }
            val jsonObj = jsonElement!!.getAsJsonObject()
            // ensure the required json array is present
            requireNotNull(jsonObj["display"]) {
              "Expected the field `display` to be an array in the JSON string but got `null`"
            }
            require(jsonObj["display"].isJsonArray()) {
              String.format("Expected the field `display` to be an array in the JSON string but got `%s`", jsonObj["display"].toString())
            }
        }

It looks like the validate function here only checks if an array is present and then declares the json as valid. It does not check if the items inside the array are actual strings. This check is just simply missing. In the LocationDataWind.validateJsonElement() function there is such a check implemented:

for (i in 0 until jsonObj.getAsJsonArray("display").size()) {
  MeteoMapLocationDataWindDisplayInner.validateJsonElement(jsonObj.getAsJsonArray("display").get(i))
} 
Suggest a fix

If there is a list of strings, the validateJsonElement() should also contain a check that all elements inside the array are in fact strings. This prevents false matches in a oneOf schema declaration.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the generated Kotlin LocationDataTemperature.validateJsonElement() entry point and compare it with LocationDataWind.validateJsonElement(), especially the handling of the display array. Reproduce the Gradle Kotlin generation using the supplied schema and verify that string-array items are checked so the oneOf response does not match the wrong type.

Written by the indexing model from the issue text.

Assessment

Tech stack
kotlin, openapi
Domain
api, tooling
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.