micronaut-projects / micronaut-openapi

Generates OpenAPI / Swagger Documentation for Micronaut projects
https://micronaut-projects.github.io/micronaut-openapi/latest/guide/index.html
Apache License 2.0
79 stars 92 forks source link

Discriminator property added to Interface not same type and not overriden causing compiler error. #1673

Closed scprek closed 3 weeks ago

scprek commented 1 month ago

Expected Behavior

interface has string

interface CancellationReasonTypesDTO {

    val version: Int
}

Subclass has Int and override

    @field:Nullable
    @field:JsonProperty(JSON_PROPERTY_VERSION)
    @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS)
    override var version: Int? = null,

OpenAPI Kotlin Generator

openapi-generator-cli generate -i test.yaml -g kotlin -o output/directory -p serializationLibrary=jackson,generateOneOfAnyOfWrappers=true,library=jvm-ktor
```kotlin @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "version", visible = true) @JsonSubTypes( JsonSubTypes.Type(value = CancellationReasonTypesV1::class, name = "-1"), JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "0"), JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "1"), JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "2"), JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "3"), JsonSubTypes.Type(value = CancellationReasonTypesV3::class, name = "4") ) interface CancellationReasonTypesDTO { /* Class CancellationReasonTypesVersion */ @get:JsonProperty("version") val version: kotlin.Int? @get:JsonProperty("reasons") val reasons: kotlin.collections.List? @get:JsonProperty("title") val title: kotlin.String? @get:JsonProperty("comments_field_prompt") val commentsFieldPrompt: kotlin.String? @get:JsonProperty("action_button_title") val actionButtonTitle: kotlin.String? @get:JsonProperty("can_cancel") val canCancel: kotlin.Boolean? @get:JsonProperty("icon_url") val iconUrl: kotlin.String? @get:JsonProperty("cancellation_charge_apply") val cancellationChargeApply: kotlin.Boolean? @get:JsonProperty("body") val body: kotlin.String? @get:JsonProperty("cancellation_charge") val cancellationCharge: Money? @get:JsonProperty("cancellation_charge_type") val cancellationChargeType: kotlin.String? @get:JsonProperty("estimate_type") val estimateType: kotlin.String? } ``` ```kotlin data class CancellationReasonTypesV2 ( /* Class CancellationReasonTypesVersion */ @field:JsonProperty("version") val version: kotlin.Int? = null, ``` ```kotlin data class CancellationReasonTypesV3 ( /* Class CancellationReasonTypesVersion */ @field:JsonProperty("version") val version: kotlin.Int? = null, ```

Actual Behaviour

@Generated("io.micronaut.openapi.generator.KotlinMicronautClientCodegen")
@JsonIgnoreProperties(
        value = ["version"], // ignore manually set version, it will be automatically generated by Jackson during serialization
        allowSetters = true // allows the version to be set during deserialization
)
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "version", visible = true)
@JsonSubTypes(
        JsonSubTypes.Type(value = CancellationReasonTypesV1::class, name = "-1"),
        JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "0"),
        JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "1"),
        JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "2"),
        JsonSubTypes.Type(value = CancellationReasonTypesV2::class, name = "3"),
        JsonSubTypes.Type(value = CancellationReasonTypesV3::class, name = "4")
)
interface CancellationReasonTypesDTO {

    val version: String
}
```kotlin @Serdeable @JsonPropertyOrder( CancellationReasonTypesV2.JSON_PROPERTY_VERSION, CancellationReasonTypesV2.JSON_PROPERTY_REASONS, CancellationReasonTypesV2.JSON_PROPERTY_TITLE, CancellationReasonTypesV2.JSON_PROPERTY_COMMENTS_FIELD_PROMPT, CancellationReasonTypesV2.JSON_PROPERTY_ACTION_BUTTON_TITLE, CancellationReasonTypesV2.JSON_PROPERTY_CAN_CANCEL, CancellationReasonTypesV2.JSON_PROPERTY_ICON_URL ) @Generated("io.micronaut.openapi.generator.KotlinMicronautClientCodegen") data class CancellationReasonTypesV2( @field:Nullable @field:JsonProperty(JSON_PROPERTY_VERSION) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var version: Int? = null, @field:Nullable @field:JsonProperty(JSON_PROPERTY_REASONS) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var reasons: List<@Valid CancellationTypeDto>? = null, @field:Nullable @field:JsonProperty(JSON_PROPERTY_TITLE) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var title: String? = null, @field:Nullable @field:JsonProperty(JSON_PROPERTY_COMMENTS_FIELD_PROMPT) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var commentsFieldPrompt: String? = null, @field:Nullable @field:JsonProperty(JSON_PROPERTY_ACTION_BUTTON_TITLE) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var actionButtonTitle: String? = null, @field:Nullable @field:JsonProperty(JSON_PROPERTY_CAN_CANCEL) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var canCancel: Boolean? = null, @field:Nullable @field:JsonProperty(JSON_PROPERTY_ICON_URL) @field:JsonInclude(JsonInclude.Include.USE_DEFAULTS) var iconUrl: String? = null, ): CancellationReasonTypesDTO { companion object { const val JSON_PROPERTY_VERSION = "version" const val JSON_PROPERTY_REASONS = "reasons" const val JSON_PROPERTY_TITLE = "title" const val JSON_PROPERTY_COMMENTS_FIELD_PROMPT = "comments_field_prompt" const val JSON_PROPERTY_ACTION_BUTTON_TITLE = "action_button_title" const val JSON_PROPERTY_CAN_CANCEL = "can_cancel" const val JSON_PROPERTY_ICON_URL = "icon_url" } } ```

Steps To Reproduce

I also tried with a reusable schema for version property

components:
  schemas:
        CancellationReasonTypesDTO:
      discriminator:
        propertyName: version
        mapping:
          '-1': '#/components/schemas/CancellationReasonTypesV1'
          '0': '#/components/schemas/CancellationReasonTypesV2'
          '1': '#/components/schemas/CancellationReasonTypesV2'
          '2': '#/components/schemas/CancellationReasonTypesV2'
          '3': '#/components/schemas/CancellationReasonTypesV2'
          '4': '#/components/schemas/CancellationReasonTypesV3'
      oneOf:
        - $ref: '#/components/schemas/CancellationReasonTypesV1'
        - $ref: '#/components/schemas/CancellationReasonTypesV2'
        - $ref: '#/components/schemas/CancellationReasonTypesV3'
    CancellationReasonTypesV1:
      allOf:
        - $ref: '#/components/schemas/CancellationTypeArrayDto' # not necessary for re-create
        - properties:
            version:
              type: integer
            title:
              type: string
            comments_field_prompt:
              type: string
            action_button_title:
              type: string
            can_cancel:
              type: boolean
          type: object

Environment Information

Noticed with both Java and Kotlin Code gen

Example Application

No response

Version

4.5.1

scprek commented 1 month ago

I also was wondering about an enum for the version as it "worked" meaning it generated something valid in the OpenAPI generator, but I didn't test it out. I'm ok making that a different ticket if it makes sense to have.

scprek commented 1 month ago

Ignore the nullable aspect, I fixed that issue by making it a required field in the schema.

altro3 commented 1 month ago

@scprek So, this is bug inside openapi-generator. It can't identificate discriminator type in this case, but, we are micronaut. That's why we fixed a bug in our generator :-)

scprek commented 1 month ago

what about the override missing? That's upstream too?

altro3 commented 1 month ago

yeah, all fixed in this PR

scprek commented 1 month ago

Oh I missed the association to the PR in the issue. Thanks!

scprek commented 1 month ago

@scprek So, this is bug inside openapi-generator. It can't identificate discriminator type in this case, but, we are micronaut. That's why we fixed a bug in our generator :-)

I guess I'm still confused by this statement. Above I showed in the collapsed details section of expected behavior that the openapi generator tool correctly identifies the discriminator type of Int.

altro3 commented 1 month ago

Well, perhaps it is. I checked the openapi generator code for Java, not for Kotlin. In any case, now our generator works correctly too