Repository: cjbooms/fabrikt
Pull request #54 (2021-06-17 23:23:34)

Improvements to class generation for complex polymorphism
 - Support for multiple discriminator keys pointing to the same class
 - Preventing the top-level use of oneOf anyOf or allOf in a schema of type object as OpenApi3 is vague on how to interpret this. Providing a good warning message when this happens

cjbooms: 👍 

Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
index f5e4484..9aac2d5 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/PropertyUtils.kt
@@ -82,6 +82,9 @@ object PropertyUtils {
                                 property.initializer("%T.%L", type, maybeDiscriminator.enumKey)
                             is PropertyInfo.DiscriminatorKey.StringKey ->
                                 property.initializer("%S", maybeDiscriminator.stringValue)
+                            else -> {
+                                property.addAnnotation(JacksonMetadata.jacksonParameterAnnotation(oasKey))
+                            }
                         }
                     } else {
                         if (isInherited) {
@@ -100,7 +103,7 @@ object PropertyUtils {
                 }
             }
 
-            if (this !is PropertyInfo.Field || !isPolymorphicDiscriminator) {
+            if (this !is PropertyInfo.Field || !isPolymorphicDiscriminator || isSubTypeDiscriminatorWithNoValue(classType)) {
                 property.initializer(name)
                 val constructorParameter: ParameterSpec.Builder = ParameterSpec.builder(name, type)
                 val default = getDefaultValue(this, parameterizedType)
@@ -112,6 +115,9 @@ object PropertyUtils {
         classBuilder.addProperty(property.build())
     }
 
+    private fun PropertyInfo.Field.isSubTypeDiscriminatorWithNoValue(classType: ClassType) =
+        classType == ClassType.SUB_MODEL && isPolymorphicDiscriminator && maybeDiscriminator == null
+
     private fun getDefaultValue(propTypeInfo: PropertyInfo, parameterizedType: TypeName): OasDefault? {
         return when (propTypeInfo) {
             is PropertyInfo.Field -> propTypeInfo.schema.default?.let {
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 8409ad1..754700d 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
@@ -27,11 +27,12 @@ import com.cjbooms.fabrikt.util.KaizenParserExtensions.getSuperType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isComplexTypedAdditionalProperties
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isEnumDefinition
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isInlinedObjectDefinition
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.isOneOfPolymorphicTypes
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isPolymorphicSubType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isPolymorphicSuperType
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isReferenceObjectDefinition
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isSimpleType
-import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKey
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKeys
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.safeName
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.toMapValueClassName
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.toModelClassName
@@ -117,6 +118,7 @@ class JacksonModelGenerator(
         val models: MutableSet<TypeSpec> =
             sourceApi.allSchemas
                 .filterNot { it.schema.isSimpleType() }
+                .filterNot { it.schema.isOneOfPolymorphicTypes() }
                 .flatMap {
                     val properties = it.schema.topLevelProperties(HTTP_SETTINGS, it.schema)
                     if (properties.isNotEmpty() || it.typeInfo is KotlinTypeInfo.Enum) {
@@ -289,13 +291,13 @@ class JacksonModelGenerator(
                 model.schema.allOfSchemas.any { allOfRef ->
                     allOfRef.name == modelName && allOfRef.discriminator == discriminator
                 }
-            }.associate {
-                discriminator.mappingKey(it.schema) to toModelType(
-                    packages.base,
-                    KotlinTypeInfo.from(it.schema, it.name)
-                )
             }
-        classBuilder.addAnnotation(polymorphicSubTypes(subTypes)).addQuarkusReflectionAnnotation()
+        val mappings = subTypes.flatMap { schemaInfo ->
+            discriminator.mappingKeys(schemaInfo.schema).map {
+                it to toModelType(packages.base, KotlinTypeInfo.from(schemaInfo.schema, schemaInfo.name))
+            }
+        }.toMap()
+        classBuilder.addAnnotation(polymorphicSubTypes(mappings)).addQuarkusReflectionAnnotation()
 
         return properties.addToClass(
             classBuilder,
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt
index a13ebea..f141175 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/PropertyInfo.kt
@@ -1,6 +1,6 @@
 package com.cjbooms.fabrikt.model
 
-import com.cjbooms.fabrikt.util.KaizenParserExtensions.getKeyIfDiscriminator
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.getKeyIfSingleDiscriminatorValue
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.hasAdditionalProperties
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.hasNoDiscriminator
 import com.cjbooms.fabrikt.util.KaizenParserExtensions.isDiscriminatorProperty
@@ -110,7 +110,7 @@ sealed class PropertyInfo {
                                 schema = property.value,
                                 isInherited = settings.markAsInherited,
                                 isPolymorphicDiscriminator = isDiscriminatorProperty(property),
-                                maybeDiscriminator = enclosingSchema?.let { this.getKeyIfDiscriminator(property, it) }
+                                maybeDiscriminator = enclosingSchema?.let { this.getKeyIfSingleDiscriminatorValue(property, it) }
                             )
                         }
                 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
index 6f507e6..8153fce 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/model/SourceApi.kt
@@ -41,16 +41,40 @@ data class SourceApi(
             .map { (key, schema) -> SchemaInfo(key, schema) }
     }
 
-    private fun validateSchemaObjects(api: OpenApi3): List<ValidationError> =
-        api.schemas.map { it.value.properties }.flatMap { it.entries }.fold(emptyList()) { lst, entry ->
+    private fun validateSchemaObjects(api: OpenApi3): List<ValidationError> {
+        val schemaErrors = api.schemas.entries.fold(emptyList<ValidationError>()) { errors, entry ->
             val name = entry.key
             val schema = entry.value
-            if (schema.type == OasType.Array.type && schema.itemsSchema.isNotDefined()) {
-                lst + listOf(ValidationError("Array type '$name' cannot be parsed to a Schema. Check your input"))
-            } else if (schema.isNotDefined()) {
-                lst + listOf(ValidationError("Property '$name' cannot be parsed to a Schema. Check your input"))
-            } else {
-                lst
-            }
+            if (schema.type == OasType.Object.type && (
+                schema.oneOfSchemas?.isNotEmpty() == true ||
+                    schema.allOfSchemas?.isNotEmpty() == true ||
+                    schema.anyOfSchemas?.isNotEmpty() == true
+                )
+            )
+                errors + listOf(
+                    ValidationError(
+                        "'$name' schema contains an invalid combination of properties and `oneOf | anyOf | allOf`. " +
+                            "Do not use properties and a combiner at the same level."
+                    )
+                )
+            else if (schema.type == OasType.Object.type && schema.oneOfSchemas?.isNotEmpty() == true)
+                errors + listOf(ValidationError("The $name object contains invalid use of both properties and `oneOf`."))
+            else if (schema.type == OasType.Object.type && schema.oneOfSchemas?.isNotEmpty() == true)
+                errors + listOf(ValidationError("The $name object contains invalid use of both properties and `oneOf`."))
+            else errors
         }
+
+        return api.schemas.map { it.value.properties }.flatMap { it.entries }
+            .fold(schemaErrors) { lst, entry ->
+                val name = entry.key
+                val schema = entry.value
+                if (schema.type == OasType.Array.type && schema.itemsSchema.isNotDefined()) {
+                    lst + listOf(ValidationError("Array type '$name' cannot be parsed to a Schema. Check your input"))
+                } else if (schema.isNotDefined()) {
+                    lst + listOf(ValidationError("Property '$name' cannot be parsed to a Schema. Check your input"))
+                } else {
+                    lst
+                }
+            }
+    }
 }
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
index 058bf12..a1a6a20 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/util/KaizenParserExtensions.kt
@@ -4,6 +4,7 @@ import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
 import com.cjbooms.fabrikt.generators.MutableSettings
 import com.cjbooms.fabrikt.model.OasType
 import com.cjbooms.fabrikt.model.PropertyInfo
+import com.cjbooms.fabrikt.util.KaizenParserExtensions.mappingKeys
 import com.cjbooms.fabrikt.util.NormalisedString.toMapValueClassName
 import com.cjbooms.fabrikt.util.NormalisedString.toModelClassName
 import com.reprezen.jsonoverlay.Overlay
@@ -115,21 +116,23 @@ object KaizenParserExtensions {
     fun Schema.isDiscriminatorProperty(prop: Map.Entry<String, Schema>): Boolean =
         discriminator?.propertyName == prop.key
 
-    fun Schema.getKeyIfDiscriminator(
+    fun Schema.getKeyIfSingleDiscriminatorValue(
         prop: Map.Entry<String, Schema>,
         enclosingSchema: Schema
     ): PropertyInfo.DiscriminatorKey? =
-        if (isDiscriminatorProperty(prop))
-            discriminator.mappingKey(enclosingSchema).let {
+        if (isDiscriminatorProperty(prop) && discriminator.mappingKeys(enclosingSchema).size == 1) {
+            discriminator.mappingKeys(enclosingSchema).first().let {
                 if (prop.value.isEnumDefinition()) PropertyInfo.DiscriminatorKey.EnumKey(it)
                 else PropertyInfo.DiscriminatorKey.StringKey(it)
             }
-        else null
+        } else null
 
-    fun Discriminator.mappingKey(enclosingSchema: Schema): String =
-        mappings?.entries?.firstOrNull {
+    fun Discriminator.mappingKeys(enclosingSchema: Schema): List<String> {
+        val keys = mappings?.entries?.filter {
             it.value.toString().contains(enclosingSchema.name)
-        }?.key ?: enclosingSchema.name.toModelClassName()
+        }?.map { it.key }
+        return if (keys.isNullOrEmpty()) listOf(enclosingSchema.name.toModelClassName()) else keys
+    }
 
     fun Schema.isInLinedObjectUnderAllOf(): Boolean =
         Overlay.of(this).pathFromRoot
@@ -161,7 +164,7 @@ object KaizenParserExtensions {
             else -> "object"
         }
 
-    private fun Schema.isOneOfPolymorphicTypes() =
+    fun Schema.isOneOfPolymorphicTypes() =
         this.oneOfSchemas?.firstOrNull()?.allOfSchemas?.firstOrNull() != null
 
     fun OpenApi3.basePath(): String =


Test patch:
diff --git a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
index 9ba4cda..8ae8fe0 100644
--- a/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
+++ b/src/test/kotlin/com/cjbooms/fabrikt/generators/ModelGeneratorTest.kt
@@ -1,5 +1,6 @@
 package com.cjbooms.fabrikt.generators
 
+import com.beust.jcommander.ParameterException
 import com.cjbooms.fabrikt.cli.CodeGenerationType
 import com.cjbooms.fabrikt.cli.ModelCodeGenOptionType
 import com.cjbooms.fabrikt.configurations.Packages
@@ -13,6 +14,7 @@ import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.BeforeEach
 import org.junit.jupiter.api.Test
 import org.junit.jupiter.api.TestInstance
+import org.junit.jupiter.api.assertThrows
 import org.junit.jupiter.params.ParameterizedTest
 import org.junit.jupiter.params.provider.MethodSource
 import java.nio.file.Paths
@@ -97,6 +99,44 @@ class ModelGeneratorTest {
         assertThat(models).isEqualTo(expectedModels)
     }
 
+    @Test
+    fun `missing array reference throws constructive message`() = assertExceptionWithMessage(
+        "/badInput/ErrorMissingRefArray.yaml",
+        "Array type 'hooks' cannot be parsed to a Schema. Check your input"
+    )
+
+    @Test
+    fun `missing object reference throws constructive message`() = assertExceptionWithMessage(
+        "/badInput/ErrorMissingRefObject.yaml",
+        "Property 'propB' cannot be parsed to a Schema. Check your input"
+    )
+
+    @Test
+    fun `mixing oneOf with object type throws constructive error`() = assertExceptionWithMessage(
+        "/badInput/ErrorMixingOneOfWithObject.yaml",
+        "schema contains an invalid combination of properties and `oneOf | anyOf | allOf`"
+    )
+    @Test
+    fun `mixing anyOf with object type throws constructive error`() = assertExceptionWithMessage(
+        "/badInput/ErrorMixingAnyOfWithObject.yaml",
+        "schema contains an invalid combination of properties and `oneOf | anyOf | allOf`"
+    )
+    @Test
+    fun `mixing allOf with object type throws constructive error`() = assertExceptionWithMessage(
+        "/badInput/ErrorMixingAllOfWithObject.yaml",
+        "schema contains an invalid combination of properties and `oneOf | anyOf | allOf`"
+    )
+
+    private fun assertExceptionWithMessage(path: String, expectedMessage: String) {
+        val spec = readTextResource(path)
+        val exception = assertThrows<ParameterException> {
+            JacksonModelGenerator(Packages("blah"), SourceApi(spec))
+                .generate()
+                .toSingleFile()
+        }
+        assertThat(exception.message).contains(expectedMessage)
+    }
+
     @Test
     fun `quarkus reflection models are generated from a full API definition when the quarkus-reflection option is set`() {
         val basePackage = "examples.quarkusReflectionModels"
diff --git a/src/test/resources/badInput/HandleMissingRefArray.yaml b/src/test/resources/badInput/ErrorMissingRefArray.yaml
similarity index 100%
rename from src/test/resources/badInput/HandleMissingRefArray.yaml
rename to src/test/resources/badInput/ErrorMissingRefArray.yaml
diff --git a/src/test/resources/badInput/HandleMissingRefObject.yaml b/src/test/resources/badInput/ErrorMissingRefObject.yaml
similarity index 100%
rename from src/test/resources/badInput/HandleMissingRefObject.yaml
rename to src/test/resources/badInput/ErrorMissingRefObject.yaml
diff --git a/src/test/resources/badInput/ErrorMixingAllOfWithObject.yaml b/src/test/resources/badInput/ErrorMixingAllOfWithObject.yaml
new file mode 100644
index 0000000..a368447
--- /dev/null
+++ b/src/test/resources/badInput/ErrorMixingAllOfWithObject.yaml
@@ -0,0 +1,31 @@
+openapi: 3.0.0
+components:
+  schemas:
+    OneOfAndTopLevelProps:
+      type: object
+      required:
+        - forth_property
+        - fifth_property
+      properties:
+        forth_property:
+          type: string
+        fifth_property:
+          type: string
+          format: date-time
+      allOf:
+        - $ref: "#/components/schemas/FirstOneB"
+        - $ref: "#/components/schemas/SecondOneB"
+
+    FirstOneB:
+      type: object
+      properties:
+        first_property:
+          type: string
+
+    SecondOneB:
+      type: object
+      properties:
+        second_property:
+          type: string
+        third_property:
+          type: string 
\ No newline at end of file
diff --git a/src/test/resources/badInput/ErrorMixingAnyOfWithObject.yaml b/src/test/resources/badInput/ErrorMixingAnyOfWithObject.yaml
new file mode 100644
index 0000000..c1151c3
--- /dev/null
+++ b/src/test/resources/badInput/ErrorMixingAnyOfWithObject.yaml
@@ -0,0 +1,31 @@
+openapi: 3.0.0
+components:
+  schemas:
+    AnyOfAndTopLevelProps:
+      type: object
+      required:
+        - forth_property
+        - fifth_property
+      properties:
+        forth_property:
+          type: string
+        fifth_property:
+          type: string
+          format: date-time
+      anyOf:
+        - $ref: "#/components/schemas/FirstOneB"
+        - $ref: "#/components/schemas/SecondOneB"
+
+    FirstOneB:
+      type: object
+      properties:
+        first_property:
+          type: string
+
+    SecondOneB:
+      type: object
+      properties:
+        second_property:
+          type: string
+        third_property:
+          type: string 
\ No newline at end of file
diff --git a/src/test/resources/badInput/ErrorMixingOneOfWithObject.yaml b/src/test/resources/badInput/ErrorMixingOneOfWithObject.yaml
new file mode 100644
index 0000000..c35255f
--- /dev/null
+++ b/src/test/resources/badInput/ErrorMixingOneOfWithObject.yaml
@@ -0,0 +1,31 @@
+openapi: 3.0.0
+components:
+  schemas:
+    OneOfAndTopLevelProps:
+      type: object
+      required:
+        - forth_property
+        - fifth_property
+      properties:
+        forth_property:
+          type: string
+        fifth_property:
+          type: string
+          format: date-time
+      oneOf:
+        - $ref: "#/components/schemas/FirstOneB"
+        - $ref: "#/components/schemas/SecondOneB"
+
+    FirstOneB:
+      type: object
+      properties:
+        first_property:
+          type: string
+
+    SecondOneB:
+      type: object
+      properties:
+        second_property:
+          type: string
+        third_property:
+          type: string 
\ No newline at end of file
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
index d572ed4..c2dbe49 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
+++ b/src/test/resources/examples/anyOfOneOfAllOf/api.yaml
@@ -47,21 +47,6 @@ components:
       properties:
         second_nested_any_of_prop:
           type: string
-          
-    OneOfAndTopLevelProps:
-      type: object
-      required:
-        - forth_property
-        - fifth_property
-      properties:
-        forth_property:
-          type: string
-        fifth_property:
-          type: string
-          format: date-time
-      oneOf:
-        - $ref: "#/components/schemas/FirstOneB"
-        - $ref: "#/components/schemas/SecondOneB"
 
     FirstOneB:
       type: object
diff --git a/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt b/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
index 22953c0..4b9b473 100644
--- a/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
+++ b/src/test/resources/examples/anyOfOneOfAllOf/models/Models.kt
@@ -2,8 +2,6 @@ package examples.anyOfOneOfAllOf.models
 
 import com.fasterxml.jackson.annotation.JsonProperty
 import java.math.BigDecimal
-import java.time.OffsetDateTime
-import javax.validation.constraints.NotNull
 import kotlin.String
 
 data class ComplexParent(
@@ -72,26 +70,6 @@ data class SecondAnyA(
     val secondNestedAnyOfProp: String? = null
 )
 
-data class OneOfAndTopLevelProps(
-    @param:JsonProperty("first_property")
-    @get:JsonProperty("first_property")
-    val firstProperty: String? = null,
-    @param:JsonProperty("second_property")
-    @get:JsonProperty("second_property")
-    val secondProperty: String? = null,
-    @param:JsonProperty("third_property")
-    @get:JsonProperty("third_property")
-    val thirdProperty: String? = null,
-    @param:JsonProperty("forth_property")
-    @get:JsonProperty("forth_property")
-    @get:NotNull
-    val forthProperty: String,
-    @param:JsonProperty("fifth_property")
-    @get:JsonProperty("fifth_property")
-    @get:NotNull
-    val fifthProperty: OffsetDateTime
-)
-
 data class FirstOneB(
     @param:JsonProperty("first_property")
     @get:JsonProperty("first_property")
diff --git a/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml b/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml
index 9cefc42..783fec1 100644
--- a/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml
+++ b/src/test/resources/examples/enumPolymorphicDiscriminator/api.yaml
@@ -10,8 +10,9 @@ components:
       discriminator:
         propertyName: some_enum
         mapping:
-          obj_one: '#/components/schemas/ConcreteImplOne'
-          obj_two: '#/components/schemas/ConcreteImplTwo'
+          obj_one_only: '#/components/schemas/ConcreteImplOne'
+          obj_two_first: '#/components/schemas/ConcreteImplTwo'
+          obj_two_second: '#/components/schemas/ConcreteImplTwo'
       properties:
         some_enum:
           $ref: '#/components/schemas/EnumDiscriminator'
@@ -35,8 +36,9 @@ components:
     EnumDiscriminator:
       type: string
       enum:
-        - obj_one
-        - obj_two
+        - obj_one_only
+        - obj_two_first
+        - obj_two_second
 
 
 
diff --git a/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt b/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
index 852392e..b7d8ec2 100644
--- a/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
+++ b/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
@@ -17,9 +17,14 @@ import kotlin.String
     JsonSubTypes.Type(
         value = ConcreteImplOne::class,
         name =
-        "obj_one"
+        "obj_one_only"
     ),
-    JsonSubTypes.Type(value = ConcreteImplTwo::class, name = "obj_two")
+    JsonSubTypes.Type(
+        value = ConcreteImplTwo::class,
+        name =
+        "obj_two_first"
+    ),
+    JsonSubTypes.Type(value = ConcreteImplTwo::class, name = "obj_two_second")
 )
 sealed class PolymorphicEnumDiscriminator() {
     abstract val someEnum: EnumDiscriminator
@@ -29,9 +34,11 @@ enum class EnumDiscriminator(
     @JsonValue
     val value: String
 ) {
-    OBJ_ONE("obj_one"),
+    OBJ_ONE_ONLY("obj_one_only"),
 
-    OBJ_TWO("obj_two");
+    OBJ_TWO_FIRST("obj_two_first"),
+
+    OBJ_TWO_SECOND("obj_two_second");
 }
 
 data class ConcreteImplOne(
@@ -41,15 +48,15 @@ data class ConcreteImplOne(
 ) : PolymorphicEnumDiscriminator() {
     @get:JsonProperty("some_enum")
     @get:NotNull
-    override val someEnum: EnumDiscriminator = EnumDiscriminator.OBJ_ONE
+    override val someEnum: EnumDiscriminator = EnumDiscriminator.OBJ_ONE_ONLY
 }
 
 data class ConcreteImplTwo(
+    @param:JsonProperty("some_enum")
+    @get:JsonProperty("some_enum")
+    @get:NotNull
+    override val someEnum: EnumDiscriminator,
     @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_TWO
-}
+) : PolymorphicEnumDiscriminator()