modelina icon indicating copy to clipboard operation
modelina copied to clipboard

Render Unions as custom type in Kotlin Generator

Open LouisXhaferi opened this issue 2 years ago • 12 comments

Reason/Context

Since JVM languages don't support type unions, the Kotlin generator currently renders unions as Any, which is Kotlin's supertype from which all classes inherit. (The Java Generator also does this, but with Object, See https://github.com/asyncapi/modelina/issues/749)

Representing unions as Any means that the user of the generated code would have to try to cast or handle the value of the respective field, each time that he uses it, which is highly inconvenient.

Therefore we should introduce a feature that renders union as a custom type actually describing possible value types, that allows the user to leverage language features to just use the field.

Description

The changes will most likely all be contained in the domain of the Kotlin generator. They require a breaking change

An optimal solution would be to implement the custom type as a sealed interface nested in the generated model.

A type union of an integer and a string could be portrayed as

data class Model(
    val value: StringIntegerUnion
) { 
  sealed interface StringIntegerUnion {
    class StringType(val value: String) : StringIntegerUnion
    class IntType(val value: Int): StringIntegerUnion
  }
}

This would then enable the user to use language features like the when condition paired with is as well as smart casts. An example of how that would be.

// imports omitted for brevity

fun main() {
    val unionString: StringIntegerUnion = StringIntegerUnion.StringType("bla bla")
    val unionInt: StringIntegerUnion = StringIntegerUnion.IntType(123)
    
    printType(unionString)
    printType(unionInt)
}

fun printType(union: StringIntegerUnion) {
    when(union) {
        is StringType -> println("is string")
        is IntType -> println("is int")
        else -> println("is something else")
    }
}

But implement how ever you like

Scope:

  • Add union support to Kotlin generator
  • Consider allowing user to override with custom type
  • Test how well it works with JSON mapping (optional)
  • Come up with deterministic naming pattern for unions
  • Consider allowing the user to give custom names? (optional)
  • Reuse unions inside of the same model, if needed

LouisXhaferi avatar Feb 03 '23 19:02 LouisXhaferi

Also one semi important note: Kotlin does not support a null type, schema languages often do. Come up with a way to handle it. Some examples type = ['string', 'null'] or type = ['string', 'integer', 'null']. How do you handle these?

Some options how you could handle it:

Option 1: Nullable* sealed interface, with nullable values in subclass constructorrs.

  sealed interface NullableStringIntegerUnion {
    class StringType(val value: String?) : StringIntegerUnion
    class IntType(val value: Int?): StringIntegerUnion
  }

Option 2: Extra NullType

  sealed interface NullableStringIntegerUnion {
    class StringType(val value: String) : NullableStringIntegerUnion
    class IntType(val value: Int): NullableStringIntegerUnion
    class NullType : NullableStringIntegerUnion
  }

Both of these are kind of a nuisance and force the user to do custom json mapping (most likely).

But there are probably wayy better ways. 😉

LouisXhaferi avatar Feb 03 '23 19:02 LouisXhaferi

I'm gonna start implementing this this weekend. Wish me luck. (jk)

LouisXhaferi avatar Mar 31 '23 13:03 LouisXhaferi

@LouisXhaferi i would forget about null for now, and just focus on the simple union, because currently null is "forgotten" i.e. you dont have access to it anywhere at the moment 😅

jonaslagoni avatar Mar 31 '23 22:03 jonaslagoni

Yeah okay I'm not gonna get it done and don't have enough time to make serious progress.

I want this to work without a preset and while I still get the TypeMapping to spit out the correct name, I'm struggling to get the unions model to be inside the model that references it.

LouisXhaferi avatar Apr 13 '23 18:04 LouisXhaferi

Maybe I can give you some pointers, the way I would achieve it:

  • Make the generator split out union models: https://github.com/asyncapi/modelina/blob/6ec55b38fc6da453e43bf69456301e183b675792/src/generators/kotlin/KotlinGenerator.ts#L96
  • Add a UnionRenderer in the same way class has
  • Let the generator check if the model is a ConstrainedUnionModel here: https://github.com/asyncapi/modelina/blob/6ec55b38fc6da453e43bf69456301e183b675792/src/generators/kotlin/KotlinGenerator.ts#L139
  • Change the type constrainer for union to be the name of the model https://github.com/asyncapi/modelina/blob/6ec55b38fc6da453e43bf69456301e183b675792/src/generators/kotlin/KotlinConstrainer.ts#L150

That should more or less do it I think 🤔

jonaslagoni avatar Apr 13 '23 19:04 jonaslagoni

Thanks for the tips, I had those things done, apart from splitObject: true. One thing I set out to, was to have the Unions inside of the actual models.

Let's see if I get that done.

LouisXhaferi avatar Apr 15 '23 14:04 LouisXhaferi

That's gonna be really tough, and not very extensible 😅 Generally the way it's setup is that each model rendering has no direct interaction with the others.

Any specific reason you want to inline the unions? 🤔

jonaslagoni avatar Apr 16 '23 11:04 jonaslagoni

This issue has been automatically marked as stale because it has not had recent activity :sleeping:

It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation.

There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model.

Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here.

Thank you for your patience :heart:

github-actions[bot] avatar Aug 15 '23 00:08 github-actions[bot]

@jonaslagoni still valid?

AnimeshKumar923 avatar Jan 07 '24 06:01 AnimeshKumar923

Yep still valid.

jonaslagoni avatar Jan 07 '24 20:01 jonaslagoni

Here is the way to achieve it: https://github.com/asyncapi/modelina/issues/1127#issuecomment-1507534472

jonaslagoni avatar Jan 07 '24 20:01 jonaslagoni

This issue has been automatically marked as stale because it has not had recent activity :sleeping:

It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation.

There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under open governance model.

Let us figure out together how to push this issue forward. Connect with us through one of many communication channels we established here.

Thank you for your patience :heart:

github-actions[bot] avatar May 07 '24 00:05 github-actions[bot]