modelina
modelina copied to clipboard
Render Unions as custom type in Kotlin Generator
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
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. 😉
I'm gonna start implementing this this weekend. Wish me luck. (jk)
@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 😅
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.
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 🤔
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.
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? 🤔
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:
@jonaslagoni still valid?
Yep still valid.
Here is the way to achieve it: https://github.com/asyncapi/modelina/issues/1127#issuecomment-1507534472
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: