Repository: cjbooms/fabrikt (kotlin)

Issue:
Polymorphism with simple $ref schemas causes broken models
Consider the following spec:

```yml
openapi: "3.0.0"
info:
  title: "API Example"
  version: "1.0"
components:
  schemas:
    FirstModelRef:
      $ref: '#/components/schemas/FirstModel'
    Content:
      type: "object"
      properties:
        id:
          type: "string"
        model_type:
          type: "string"
          enum:
            - "first_model"
            - "second_model"
      discriminator:
        propertyName: "model_type"
        mapping:
          first_model: "#/components/schemas/FirstModel"
          second_model: "#/components/schemas/SecondModel"

    FirstModel:
      allOf:
        - $ref: "#/components/schemas/Content"
        - type: "object"
          properties:
            a:
              type: string
    SecondModel:
      allOf:
        - $ref: "#/components/schemas/Content"
        - type: "object"
          properties:
            b:
              type: string
```

which generates the following `Content.kt`:

```kotlin
package com.example.models

import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import javax.validation.constraints.NotNull
import kotlin.String

@JsonTypeInfo(
  use = JsonTypeInfo.Id.NAME,
  include = JsonTypeInfo.As.EXISTING_PROPERTY,
  property = "model_type",
  visible = true
)
@JsonSubTypes(JsonSubTypes.Type(value = FirstModel::class, name =
    "first_model"),JsonSubTypes.Type(value = SecondModel::class, name = "second_model"))
sealed class Content(
  open val id: String? = null
) {
  abstract val modelType: ContentModelType
}

data class FirstModelRef(
  @param:JsonProperty("id")
  @get:JsonProperty("id")
  override val id: String? = null,
  @param:JsonProperty("a")
  @get:JsonProperty("a")
  val a: String? = null
) : Content(id) {
  @param:JsonProperty("model_type")
  @get:JsonProperty("model_type")
  @get:NotNull
  override val modelType: ContentModelType
}

data class FirstModel(
  @param:JsonProperty("id")
  @get:JsonProperty("id")
  override val id: String? = null,
  @param:JsonProperty("a")
  @get:JsonProperty("a")
  val a: String? = null
) : Content(id) {
  @get:JsonProperty("model_type")
  @get:NotNull
  override val modelType: ContentModelType = ContentModelType.FIRST_MODEL
}

data class SecondModel(
  @param:JsonProperty("id")
  @get:JsonProperty("id")
  override val id: String? = null,
  @param:JsonProperty("b")
  @get:JsonProperty("b")
  val b: String? = null
) : Content(id) {
  @get:JsonProperty("model_type")
  @get:NotNull
  override val modelType: ContentModelType = ContentModelType.SECOND_MODEL
}
```

From a spec perspective, `FirstModel` and `FirstModelRef` should be the same but their data classes in the generated code are not. Even more importantly, the class `FirstModelRef` contains errors as the `modelType` variable is marked as immutable but not assigned a value.

The culprit is `Map<String, PropertyInfo.DiscriminatorKey>?.getDiscriminatorMappings`, which returns an empty list for `FirstModelRef`.

Pull request:
Closes #203

Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
index de60641c..65194128 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
@@ -26,7 +26,6 @@ data class ClassSettings(
 
 object PropertyUtils {
     fun PropertyInfo.addToClass(
-        modelName: String,
         schemaName: String,
         type: TypeName,
         parameterizedType: TypeName,
@@ -87,7 +86,7 @@ object PropertyUtils {
                 ClassSettings.PolymorphyType.SUB -> {
                     if (this is PropertyInfo.Field && isPolymorphicDiscriminator) {
                         property.addModifiers(KModifier.OVERRIDE)
-                        val discriminators = maybeDiscriminator.getDiscriminatorMappings(schemaName, modelName)
+                        val discriminators = maybeDiscriminator.getDiscriminatorMappings(schemaName)
                         if (discriminators.size == 1) {
                             when (val discriminator = discriminators.first()) {
                                 is PropertyInfo.DiscriminatorKey.EnumKey ->
@@ -120,7 +119,7 @@ object PropertyUtils {
             if (this !is PropertyInfo.Field ||
                 !isPolymorphicDiscriminator ||
                 isSubTypeDiscriminatorWithNoValue(classSettings) ||
-                isSubTypeDiscriminatorWithMultipleValues(classSettings, modelName, schemaName)
+                isSubTypeDiscriminatorWithMultipleValues(classSettings, schemaName)
             ) {
                 property.initializer(name)
                 val constructorParameter: ParameterSpec.Builder = ParameterSpec.builder(name, wrappedType)
@@ -145,11 +144,10 @@ object PropertyUtils {
     }
 
     private fun Map<String, PropertyInfo.DiscriminatorKey>?.getDiscriminatorMappings(
-        modelName: String,
         schemaName: String,
     ): List<PropertyInfo.DiscriminatorKey> =
         this?.filter {
-            it.value.stringValue == schemaName || it.value.modelName == modelName || it.value.modelName == schemaName
+            it.value.stringValue == schemaName || it.value.modelName == schemaName
         }?.map { it.value }.orEmpty()
 
     private fun PropertyInfo.Field.isSubTypeDiscriminatorWithNoValue(classType: ClassSettings) =
@@ -159,12 +157,11 @@ object PropertyUtils {
 
     private fun PropertyInfo.Field.isSubTypeDiscriminatorWithMultipleValues(
         classType: ClassSettings,
-        modelName: String,
         schemaName: String,
     ) =
         classType.polymorphyType == ClassSettings.PolymorphyType.SUB &&
             isPolymorphicDiscriminator &&
-            maybeDiscriminator.getDiscriminatorMappings(modelName, schemaName).size > 1
+            maybeDiscriminator.getDiscriminatorMappings(schemaName).size > 1
 
     private fun getDefaultValue(propTypeInfo: PropertyInfo, parameterizedType: TypeName): OasDefault? {
         return when (propTypeInfo) {
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
index 712e0e8e..621d5182 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
@@ -28,6 +28,7 @@ import com.cjbooms.fabrikt.model.PropertyInfo.Companion.topLevelProperties
 import com.cjbooms.fabrikt.model.SchemaInfo
 import com.cjbooms.fabrikt.model.SourceApi
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.getDiscriminatorForInLinedObjectUnderAllOf
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSchemaRefName
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSuperType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isComplexTypedAdditionalProperties
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedEnumDefinition
@@ -179,7 +180,7 @@ class JacksonModelGenerator(
         allSchemas: List<SchemaInfo>,
     ): TypeSpec {
         val modelName = schemaInfo.name.toModelClassName()
-        val schemaName = schemaInfo.name
+        val schemaName = schemaInfo.schema.getSchemaRefName()
         return when {
             schemaInfo.schema.isOneOfSuperInterface() && SEALED_INTERFACES_FOR_ONE_OF in options -> oneOfSuperInterface(
                 modelName,
@@ -462,7 +463,6 @@ class JacksonModelGenerator(
 
         if (!generateObject) {
             filteredProperties.addToClass(
-            modelName = modelName,
             schemaName = schemaName,
             classBuilder = classBuilder,
             classType = ClassSettings(ClassSettings.PolymorphyType.NONE, extensions.hasJsonMergePatchExtension),
@@ -483,7 +483,6 @@ class JacksonModelGenerator(
     ): TypeSpec = with(FunSpec.constructorBuilder()) {
         TypeSpec.classBuilder(generatedType(packages.base, modelName))
             .buildPolymorphicSubType(
-                modelName,
                 schemaName,
                 properties.filter(PropertyInfo::isInherited),
                 superType,
@@ -598,7 +597,6 @@ class JacksonModelGenerator(
             .addMicronautReflectionAnnotation()
 
         properties.addToClass(
-            modelName,
             schemaName,
             constructorBuilder,
             this,
@@ -616,10 +614,9 @@ class JacksonModelGenerator(
         extensions: Map<String, Any>,
         oneOfSuperInterfaces: Set<SchemaInfo>,
     ): TypeSpec = TypeSpec.classBuilder(generatedType(packages.base, modelName))
-        .buildPolymorphicSubType(modelName, schemaName, properties, superType, extensions, oneOfSuperInterfaces).build()
+        .buildPolymorphicSubType(schemaName, properties, superType, extensions, oneOfSuperInterfaces).build()
 
     private fun TypeSpec.Builder.buildPolymorphicSubType(
-        modelName: String,
         schemaName: String,
         allProperties: Collection<PropertyInfo>,
         superType: SchemaInfo,
@@ -652,7 +649,6 @@ class JacksonModelGenerator(
         } ?: allProperties
 
         properties.addToClass(
-            modelName,
             schemaName,
             constructorBuilder,
             this,
@@ -662,7 +658,6 @@ class JacksonModelGenerator(
     }
 
     private fun Collection<PropertyInfo>.addToClass(
-        modelName: String,
         schemaName: String,
         constructorBuilder: FunSpec.Builder = FunSpec.constructorBuilder(),
         classBuilder: TypeSpec.Builder,
@@ -670,7 +665,6 @@ class JacksonModelGenerator(
     ): TypeSpec.Builder {
         this.forEach {
             it.addToClass(
-                modelName,
                 schemaName,
                 toModelType(
                     packages.base,
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
index 4924c24a..961c9ed7 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
@@ -140,6 +140,8 @@ object KaizenParserExtensions {
             requiredFields.contains(prop.key) || isDiscriminatorProperty(prop) // A discriminator property should be required
         }
 
+    fun Schema.getSchemaRefName() = Overlay.of(this).jsonReference.split("/").last()
+
     fun Schema.isDiscriminatorProperty(prop: Map.Entry<String, Schema>): Boolean =
         discriminator?.propertyName == prop.key
 


Test patch:
diff --git a/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml b/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml
index 27a6d3e3..9198f1f6 100644
--- a/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml
+++ b/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml
@@ -23,6 +23,9 @@ components:
         some_enum:
           $ref: '#/components/schemas/EnumDiscriminator'
 
+    ConcreteImplOneRef:
+      $ref: '#/components/schemas/ConcreteImplOne'
+
     ConcreteImplOne:
       allOf:
         - $ref: '#/components/schemas/polymorphicEnumDiscriminator'
@@ -38,7 +41,7 @@ components:
           properties:
             some_prop:
               type: string
-              
+
     concrete_impl_three:
       allOf:
         - $ref: '#/components/schemas/polymorphicEnumDiscriminator'
diff --git a/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt b/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
index 86d3e406..5c53a4f1 100644
--- a/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
+++ b/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
@@ -19,6 +19,16 @@ data class ConcreteImplOne(
     override val someEnum: EnumDiscriminator = EnumDiscriminator.OBJ_ONE_ONLY
 }
 
+data class ConcreteImplOneRef(
+    @param:JsonProperty("some_prop")
+    @get:JsonProperty("some_prop")
+    val someProp: String? = null
+) : PolymorphicEnumDiscriminator() {
+    @get:JsonProperty("some_enum")
+    @get:NotNull
+    override val someEnum: EnumDiscriminator = EnumDiscriminator.OBJ_ONE_ONLY
+}
+
 class ConcreteImplThree() : PolymorphicEnumDiscriminator() {
     @get:JsonProperty("some_enum")
     @get:NotNull
diff --git a/src/test/resources/examples/polymorphicModels/api.yaml b/src/test/resources/examples/polymorphicModels/api.yaml
index ee96efc0..c61b5c1f 100644
--- a/src/test/resources/examples/polymorphicModels/api.yaml
+++ b/src/test/resources/examples/polymorphicModels/api.yaml
@@ -1,6 +1,12 @@
 openapi: 3.0.0
 components:
   schemas:
+    PolymorphicTypeOneRef:
+      $ref: "#/components/schemas/PolymorphicTypeOne"
+    PolymorphicTypeOneAnotherRef:
+      $ref: "#/components/schemas/PolymorphicTypeOne"
+    PolymorphicTypeTwoRef:
+      $ref: "#/components/schemas/polymorphic_type_two"
     PolymorphicSuperType:
       type: object
       discriminator:
@@ -32,7 +38,7 @@ components:
             child_two_age:
               type: integer
               format: int32
-              
+
     AnotherObject:
       type: object
       properties:
diff --git a/src/test/resources/examples/polymorphicModels/models/Models.kt b/src/test/resources/examples/polymorphicModels/models/Models.kt
index 4d5ba56e..64af0394 100644
--- a/src/test/resources/examples/polymorphicModels/models/Models.kt
+++ b/src/test/resources/examples/polymorphicModels/models/Models.kt
@@ -56,6 +56,42 @@ data class PolymorphicTypeOne(
     override val generation: String = "PolymorphicTypeOne"
 }
 
+data class PolymorphicTypeOneAnotherRef(
+    @param:JsonProperty("first_name")
+    @get:JsonProperty("first_name")
+    @get:NotNull
+    override val firstName: String,
+    @param:JsonProperty("last_name")
+    @get:JsonProperty("last_name")
+    @get:NotNull
+    override val lastName: String,
+    @param:JsonProperty("child_one_name")
+    @get:JsonProperty("child_one_name")
+    val childOneName: String? = null
+) : PolymorphicSuperType(firstName, lastName) {
+    @get:JsonProperty("generation")
+    @get:NotNull
+    override val generation: String = "PolymorphicTypeOne"
+}
+
+data class PolymorphicTypeOneRef(
+    @param:JsonProperty("first_name")
+    @get:JsonProperty("first_name")
+    @get:NotNull
+    override val firstName: String,
+    @param:JsonProperty("last_name")
+    @get:JsonProperty("last_name")
+    @get:NotNull
+    override val lastName: String,
+    @param:JsonProperty("child_one_name")
+    @get:JsonProperty("child_one_name")
+    val childOneName: String? = null
+) : PolymorphicSuperType(firstName, lastName) {
+    @get:JsonProperty("generation")
+    @get:NotNull
+    override val generation: String = "PolymorphicTypeOne"
+}
+
 data class PolymorphicTypeTwo(
     @param:JsonProperty("first_name")
     @get:JsonProperty("first_name")
@@ -76,3 +112,24 @@ data class PolymorphicTypeTwo(
     @get:NotNull
     override val generation: String = "polymorphic_type_two"
 }
+
+data class PolymorphicTypeTwoRef(
+    @param:JsonProperty("first_name")
+    @get:JsonProperty("first_name")
+    @get:NotNull
+    override val firstName: String,
+    @param:JsonProperty("last_name")
+    @get:JsonProperty("last_name")
+    @get:NotNull
+    override val lastName: String,
+    @param:JsonProperty("some_integer_propery")
+    @get:JsonProperty("some_integer_propery")
+    val someIntegerPropery: Int? = null,
+    @param:JsonProperty("child_two_age")
+    @get:JsonProperty("child_two_age")
+    val childTwoAge: Int? = null
+) : PolymorphicSuperType(firstName, lastName) {
+    @get:JsonProperty("generation")
+    @get:NotNull
+    override val generation: String = "polymorphic_type_two"
+}
diff --git a/src/test/resources/examples/polymorphicModels/sealed/models/Models.kt b/src/test/resources/examples/polymorphicModels/sealed/models/Models.kt
index 7ca84fc7..b2236d98 100644
--- a/src/test/resources/examples/polymorphicModels/sealed/models/Models.kt
+++ b/src/test/resources/examples/polymorphicModels/sealed/models/Models.kt
@@ -32,6 +32,63 @@ sealed class PolymorphicSuperType(
     abstract val generation: String
 }
 
+data class PolymorphicTypeOneRef(
+    @param:JsonProperty("first_name")
+    @get:JsonProperty("first_name")
+    @get:NotNull
+    override val firstName: String,
+    @param:JsonProperty("last_name")
+    @get:JsonProperty("last_name")
+    @get:NotNull
+    override val lastName: String,
+    @param:JsonProperty("child_one_name")
+    @get:JsonProperty("child_one_name")
+    val childOneName: String? = null
+) : PolymorphicSuperType(firstName, lastName) {
+    @get:JsonProperty("generation")
+    @get:NotNull
+    override val generation: String = "PolymorphicTypeOne"
+}
+
+data class PolymorphicTypeOneAnotherRef(
+    @param:JsonProperty("first_name")
+    @get:JsonProperty("first_name")
+    @get:NotNull
+    override val firstName: String,
+    @param:JsonProperty("last_name")
+    @get:JsonProperty("last_name")
+    @get:NotNull
+    override val lastName: String,
+    @param:JsonProperty("child_one_name")
+    @get:JsonProperty("child_one_name")
+    val childOneName: String? = null
+) : PolymorphicSuperType(firstName, lastName) {
+    @get:JsonProperty("generation")
+    @get:NotNull
+    override val generation: String = "PolymorphicTypeOne"
+}
+
+data class PolymorphicTypeTwoRef(
+    @param:JsonProperty("first_name")
+    @get:JsonProperty("first_name")
+    @get:NotNull
+    override val firstName: String,
+    @param:JsonProperty("last_name")
+    @get:JsonProperty("last_name")
+    @get:NotNull
+    override val lastName: String,
+    @param:JsonProperty("some_integer_propery")
+    @get:JsonProperty("some_integer_propery")
+    val someIntegerPropery: Int? = null,
+    @param:JsonProperty("child_two_age")
+    @get:JsonProperty("child_two_age")
+    val childTwoAge: Int? = null
+) : PolymorphicSuperType(firstName, lastName) {
+    @get:JsonProperty("generation")
+    @get:NotNull
+    override val generation: String = "polymorphic_type_two"
+}
+
 data class PolymorphicTypeOne(
     @param:JsonProperty("first_name")
     @get:JsonProperty("first_name")