Skip to content
GitLab
Projects Groups Snippets
  • /
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
    • Contribute to GitLab
  • Sign in / Register
  • O openapi-generator
  • Project information
    • Project information
    • Activity
    • Labels
    • Members
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
  • Issues 3,476
    • Issues 3,476
    • List
    • Boards
    • Service Desk
    • Milestones
  • Merge requests 402
    • Merge requests 402
  • CI/CD
    • CI/CD
    • Pipelines
    • Jobs
    • Schedules
  • Deployments
    • Deployments
    • Environments
    • Releases
  • Packages and registries
    • Packages and registries
    • Package Registry
    • Infrastructure Registry
  • Monitor
    • Monitor
    • Incidents
  • Analytics
    • Analytics
    • Value stream
    • CI/CD
    • Repository
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
Collapse sidebar
  • OpenAPI Tools
  • openapi-generator
  • Issues
  • #14657
Closed
Open
Issue created Feb 09, 2023 by Administrator@rootContributor

[REQ] Kotlin, request for feedback on proposed model pattern for 3.1.0 spec processing

Created by: spacether

How does the kotlin client generator handle properties with invalid names? One could inherit from HashMap and make a class where one could create enums for each known key. And use those to perform type safe extraction of map values. Here is an example which works for:

  • keys with invalid kotlin variable names
  • allows type definition of additional properties also
  • allows normal map usage w/ string keys too

For example given this openapi component schema:

MyMap:
  type: object
  properties:
    #:
      type: string
    "1":
      type: integer
    "none":
      type: "null",
  additionalProperties:
    type: boolean

Here is a sample Kotlin Class:

interface MyMapKey {
    fun value(): String
}

class MyMap: HashMap<String, Any?>() {
    // this is an example of how one could write an openapi type object model
    class Keys_ {
        // red, one , none
        enum class DollarSignEnum(val value: String): MyMapKey {
            DOLLAR_SIGN("$");  // value type is String
            override fun value(): String {
                return this.value
            }
        }

        enum class OneEnum(val value: String): MyMapKey {
            ONE("1");  // value type is Int
            override fun value(): String {
                return this.value
            }
        }

        enum class NoneEnum(val value: String): MyMapKey {
            NONE("none");  // value type is null
            override fun value(): String {
                return this.value
            }
        }

        companion object {
            val DOLLAR_SIGN = DollarSignEnum.DOLLAR_SIGN
            val ONE = OneEnum.ONE
            val NONE = NoneEnum.NONE
            val propertyKeyToEnum: Map<String, MyMapKey> = mapOf(
                "$" to DOLLAR_SIGN,
                "1" to ONE,
                "none" to NONE
            )
            fun isAdditionalProperty(propertyName: String): Boolean {
                return !propertyKeyToEnum.containsKey(propertyName)
            }
        }
    }

    operator fun get(key: Keys_.DollarSignEnum): String {
        return super.get(key.value).toString()
    }

    operator fun get(key: Keys_.OneEnum): Int {
        return super.get(key.value) as Int
    }

    operator fun get(key: MyMapKey): Any? {
        return super.get(key.value())
    }

    fun getAdditionalProperty(key: String): Boolean {
        if (Keys_.isAdditionalProperty(key)) {
            return super.get(key) as Boolean
        }
        throw IllegalArgumentException("key was not an additional property")
    }

    operator fun get(key: Keys_.NoneEnum): Nothing? {
        val value = super.get(key.value)
        if (value == null) {
            return null
        }
        throw IllegalStateException("value was not null and it must be null")
    }
}

Example usage code:

import org.junit.Test

class MyMapTest {
    @Test
    fun testMyMap() {
        val mapData = mapOf(
            "$" to "hi",
            "1" to 123,
            "none" to null,
            "add-Prop" to true
        )
        val castMap = MyMap()
        castMap.putAll(mapData)

        val dollarSignVal = castMap[MyMap.Keys_.DOLLAR_SIGN] // type is String
        val oneVal = castMap[MyMap.Keys_.ONE] // type is Int
        val noneVal = castMap[MyMap.Keys_.NONE] // type is Nothing?
        if (MyMap.Keys_.isAdditionalProperty("add-Prop")) {
            val addPropVal = castMap.getAdditionalProperty("add-Prop")  // type is Boolean
        }

        val dollarSignValue = castMap["$"] // type is Any? and is actually String
        val oneValue = castMap["1"] // type is Any? and is actually Int
        val noneValue = castMap["none"] // type is Any? and is actually null
        val addPropValue = castMap.getAdditionalProperty("add-Prop")  // type is Any? and is actually Boolean

    }
}

What do people think of this solution? This could be a way of implementing type object models which allows:

  • typed access
  • typed additional property access
  • key names to have invalid Kotlin names + still have typed access
  • object models would have normal map methods also and required properties could have accessor functions defined for them when the keys have valid kotlin variable names.

Note: A solution like this would also work for ArrayList (json schema type array) in openapi 3.1.0. In that version of the spec, array values depend upon the index being retrieved via prefixItems + items definitions.

Assignee
Assign to
Time tracking