diff --git a/core/src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt b/core/src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt index 1485f271..0a63ae47 100644 --- a/core/src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt +++ b/core/src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt @@ -42,7 +42,7 @@ public sealed class Avro( private val schemaCache: Cache = WeakKeyCache() internal val recordResolver = RecordResolver(this) - internal val polymorphicResolver = PolymorphicResolver(serializersModule) + internal val polymorphicResolver = PolymorphicResolver(serializersModule) { schema(it).fullName } internal val enumResolver = EnumResolver() public companion object Default : Avro( diff --git a/core/src/main/kotlin/com/github/avrokotlin/avro4k/internal/PolymorphicResolver.kt b/core/src/main/kotlin/com/github/avrokotlin/avro4k/internal/PolymorphicResolver.kt index 462fa2db..d4c12ee3 100644 --- a/core/src/main/kotlin/com/github/avrokotlin/avro4k/internal/PolymorphicResolver.kt +++ b/core/src/main/kotlin/com/github/avrokotlin/avro4k/internal/PolymorphicResolver.kt @@ -4,7 +4,10 @@ import com.github.avrokotlin.avro4k.AvroAlias import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.modules.SerializersModule -internal class PolymorphicResolver(private val serializersModule: SerializersModule) { +internal class PolymorphicResolver( + private val serializersModule: SerializersModule, + private val schemaNameResolver: (SerialDescriptor) -> String, +) { private val cache = WeakKeyCache>() fun getFullNamesAndAliasesToSerialName(descriptor: SerialDescriptor): Map { @@ -12,7 +15,7 @@ internal class PolymorphicResolver(private val serializersModule: SerializersMod descriptor.possibleSerializationSubclasses(serializersModule) .flatMap { sequence { - yield(it.nonNullSerialName to it.nonNullSerialName) + yield(schemaNameResolver(it) to it.nonNullSerialName) it.findAnnotation()?.value?.forEach { alias -> yield(alias to it.nonNullSerialName) } diff --git a/core/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/SealedClassEncodingTest.kt b/core/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/SealedClassEncodingTest.kt index 83c20bb2..6f6196f9 100644 --- a/core/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/SealedClassEncodingTest.kt +++ b/core/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/SealedClassEncodingTest.kt @@ -7,6 +7,7 @@ import com.github.avrokotlin.avro4k.recordWithSchema import com.github.avrokotlin.avro4k.schema import io.kotest.core.spec.style.StringSpec import kotlinx.serialization.Serializable +import org.apache.avro.generic.GenericData internal class SealedClassEncodingTest : StringSpec({ "encode/decode sealed classes" { @@ -15,6 +16,13 @@ internal class SealedClassEncodingTest : StringSpec({ AvroAssertions.assertThat(Operation.Binary.Add(1, 2)) .isEncodedAs(recordWithSchema(Avro.schema(), 1, 2)) } + "encode/decode sealed class union with value class primitive, enum and record subtypes" { + val shapeSchema = Avro.schema() + val colorSchema = shapeSchema.types.first { it.name == "Color" } + AvroAssertions.assertThat(Shape.Label("hello")).isEncodedAs("hello") + AvroAssertions.assertThat(Shape.Color.RED).isEncodedAs(GenericData.get().createEnum("RED", colorSchema)) + AvroAssertions.assertThat(Shape.Circle(5)).isEncodedAs(recordWithSchema(Avro.schema(), 5)) + } "encode/decode nullable sealed classes" { AvroAssertions.assertThat(ReferencingNullableSealedClass(Operation.Binary.Add(1, 2))) .isEncodedAs(record(recordWithSchema(Avro.schema(), 1, 2))) @@ -27,6 +35,19 @@ internal class SealedClassEncodingTest : StringSpec({ .isEncodedAs(null) } }) { + @Serializable + private sealed interface Shape { + @JvmInline + @Serializable + value class Label(val value: String) : Shape + + @Serializable + enum class Color : Shape { RED, BLUE } + + @Serializable + data class Circle(val radius: Int) : Shape + } + @Serializable private data class ReferencingSealedClass( val notNullable: Operation, diff --git a/kotlin-generator/src/main/kotlin/com/github/avrokotlin/avro4k/KotlinGenerator.kt b/kotlin-generator/src/main/kotlin/com/github/avrokotlin/avro4k/KotlinGenerator.kt index 71f2e660..700f86de 100644 --- a/kotlin-generator/src/main/kotlin/com/github/avrokotlin/avro4k/KotlinGenerator.kt +++ b/kotlin-generator/src/main/kotlin/com/github/avrokotlin/avro4k/KotlinGenerator.kt @@ -332,6 +332,7 @@ public class KotlinGenerator( * ```kotlin * @Serializable * sealed interface Union { + * @AvroAlias("") * @JvmInline * @Serializable * value class For(val value: ) : Union diff --git a/kotlin-generator/src/test/expected-sources/complex-union-in-record/ComplexUnionInRecord.kt b/kotlin-generator/src/test/expected-sources/complex-union-in-record/ComplexUnionInRecord.kt index 4491e5d6..9c591654 100644 --- a/kotlin-generator/src/test/expected-sources/complex-union-in-record/ComplexUnionInRecord.kt +++ b/kotlin-generator/src/test/expected-sources/complex-union-in-record/ComplexUnionInRecord.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.AvroDefault import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi @@ -25,18 +26,21 @@ public data class ComplexUnionInRecord( @Serializable @AvroGenerated("""["null",{"type":"record","name":"NestedRecord","fields":[{"name":"id","type":"string"},{"name":"value","type":"int"}]},{"type":"enum","name":"Status","symbols":["ACTIVE","INACTIVE","PENDING"]},{"type":"array","items":"string"}]""") public sealed interface TheFieldUnion { + @AvroAlias("NestedRecord") @JvmInline @Serializable public value class ForNestedRecord( public val `value`: NestedRecord, ) : TheFieldUnion + @AvroAlias("Status") @JvmInline @Serializable public value class ForStatus( public val `value`: Status, ) : TheFieldUnion + @AvroAlias("array") @JvmInline @Serializable public value class ForArray( diff --git a/kotlin-generator/src/test/expected-sources/field-naming-camel-case/ns/CamelCaseRecord.kt b/kotlin-generator/src/test/expected-sources/field-naming-camel-case/ns/CamelCaseRecord.kt index 2d77a8a3..7ce24da6 100644 --- a/kotlin-generator/src/test/expected-sources/field-naming-camel-case/ns/CamelCaseRecord.kt +++ b/kotlin-generator/src/test/expected-sources/field-naming-camel-case/ns/CamelCaseRecord.kt @@ -48,12 +48,14 @@ public data class CamelCaseRecord( @Serializable @AvroGenerated("""["string",{"type":"enum","name":"StatusFlag","namespace":"ns","symbols":["ACTIVE","INACTIVE"]},"null"]""") public sealed interface AccountStatusUnion { + @AvroAlias("string") @JvmInline @Serializable public value class ForString( public val `value`: String, ) : AccountStatusUnion + @AvroAlias("ns.StatusFlag") @JvmInline @Serializable public value class ForStatusFlag( diff --git a/kotlin-generator/src/test/expected-sources/root-complex-union/TestSchema.kt b/kotlin-generator/src/test/expected-sources/root-complex-union/TestSchema.kt index 259f7bbc..51ad0bd6 100644 --- a/kotlin-generator/src/test/expected-sources/root-complex-union/TestSchema.kt +++ b/kotlin-generator/src/test/expected-sources/root-complex-union/TestSchema.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.AvroFixed import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi @@ -20,30 +21,35 @@ import kotlinx.serialization.Serializable @Serializable @AvroGenerated("""["null","string","int",{"type":"record","name":"NestedRecord","fields":[{"name":"field","type":"string","doc":"field doc"}]},{"type":"enum","name":"AnEnum","symbols":["A","B","C"]},{"type":"fixed","name":"AFixed","size":5},{"type":"array","items":"int"},{"type":"map","values":["null","double"]}]""") public sealed interface TestSchema { + @AvroAlias("string") @JvmInline @Serializable public value class ForString( public val `value`: String, ) : TestSchema + @AvroAlias("int") @JvmInline @Serializable public value class ForInt( public val `value`: Int, ) : TestSchema + @AvroAlias("NestedRecord") @JvmInline @Serializable public value class ForNestedRecord( public val `value`: NestedRecord, ) : TestSchema + @AvroAlias("AnEnum") @JvmInline @Serializable public value class ForAnEnum( public val `value`: AnEnum, ) : TestSchema + @AvroAlias("AFixed") @JvmInline @Serializable public value class ForAFixed( @@ -51,12 +57,14 @@ public sealed interface TestSchema { public val `value`: ByteArray, ) : TestSchema + @AvroAlias("array") @JvmInline @Serializable public value class ForArray( public val `value`: List, ) : TestSchema + @AvroAlias("map") @JvmInline @Serializable public value class ForMap( diff --git a/kotlin-generator/src/test/expected-sources/root-map/TestSchemaMapUnion.kt b/kotlin-generator/src/test/expected-sources/root-map/TestSchemaMapUnion.kt index c7858df8..9752e7b7 100644 --- a/kotlin-generator/src/test/expected-sources/root-map/TestSchemaMapUnion.kt +++ b/kotlin-generator/src/test/expected-sources/root-map/TestSchemaMapUnion.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi import com.github.avrokotlin.avro4k.`internal`.AvroGenerated @@ -15,12 +16,14 @@ import kotlinx.serialization.Serializable @Serializable @AvroGenerated("""["double","null","int"]""") public sealed interface TestSchemaMapUnion { + @AvroAlias("double") @JvmInline @Serializable public value class ForDouble( public val `value`: Double, ) : TestSchemaMapUnion + @AvroAlias("int") @JvmInline @Serializable public value class ForInt( diff --git a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchema.kt b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchema.kt index 052dabf3..bc587921 100644 --- a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchema.kt +++ b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchema.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi import com.github.avrokotlin.avro4k.`internal`.AvroGenerated @@ -17,18 +18,21 @@ import kotlinx.serialization.Serializable @Serializable @AvroGenerated("""["int","null",{"type":"map","values":["string","null",{"type":"array","items":["long","null",{"type":"map","values":["boolean","null"]}]}]},{"type":"array","items":["long","null","double"]}]""") public sealed interface TestSchema { + @AvroAlias("int") @JvmInline @Serializable public value class ForInt( public val `value`: Int, ) : TestSchema + @AvroAlias("map") @JvmInline @Serializable public value class ForMap( public val `value`: Map, ) : TestSchema + @AvroAlias("array") @JvmInline @Serializable public value class ForArray( diff --git a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaArrayUnion.kt b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaArrayUnion.kt index f9409735..c68a4198 100644 --- a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaArrayUnion.kt +++ b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaArrayUnion.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi import com.github.avrokotlin.avro4k.`internal`.AvroGenerated @@ -15,12 +16,14 @@ import kotlinx.serialization.Serializable @Serializable @AvroGenerated("""["long","null","double"]""") public sealed interface TestSchemaArrayUnion { + @AvroAlias("long") @JvmInline @Serializable public value class ForLong( public val `value`: Long, ) : TestSchemaArrayUnion + @AvroAlias("double") @JvmInline @Serializable public value class ForDouble( diff --git a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapArrayUnion.kt b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapArrayUnion.kt index 57773607..86504c2f 100644 --- a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapArrayUnion.kt +++ b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapArrayUnion.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi import com.github.avrokotlin.avro4k.`internal`.AvroGenerated @@ -17,12 +18,14 @@ import kotlinx.serialization.Serializable @Serializable @AvroGenerated("""["long","null",{"type":"map","values":["boolean","null"]}]""") public sealed interface TestSchemaMapArrayUnion { + @AvroAlias("long") @JvmInline @Serializable public value class ForLong( public val `value`: Long, ) : TestSchemaMapArrayUnion + @AvroAlias("map") @JvmInline @Serializable public value class ForMap( diff --git a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapUnion.kt b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapUnion.kt index 50f01be4..1fee3616 100644 --- a/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapUnion.kt +++ b/kotlin-generator/src/test/expected-sources/root-union-with-nested-map-and-array-unions/TestSchemaMapUnion.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi import com.github.avrokotlin.avro4k.`internal`.AvroGenerated @@ -15,12 +16,14 @@ import kotlinx.serialization.Serializable @Serializable @AvroGenerated("""["string","null",{"type":"array","items":["long","null",{"type":"map","values":["boolean","null"]}]}]""") public sealed interface TestSchemaMapUnion { + @AvroAlias("string") @JvmInline @Serializable public value class ForString( public val `value`: String, ) : TestSchemaMapUnion + @AvroAlias("array") @JvmInline @Serializable public value class ForArray( diff --git a/kotlin-generator/src/test/expected-sources/types-across-multiple-files/Union.kt b/kotlin-generator/src/test/expected-sources/types-across-multiple-files/Union.kt index de1eeb35..0a5affbb 100644 --- a/kotlin-generator/src/test/expected-sources/types-across-multiple-files/Union.kt +++ b/kotlin-generator/src/test/expected-sources/types-across-multiple-files/Union.kt @@ -3,6 +3,7 @@ ExperimentalAvro4kApi::class, ) +import com.github.avrokotlin.avro4k.AvroAlias import com.github.avrokotlin.avro4k.AvroFixed import com.github.avrokotlin.avro4k.ExperimentalAvro4kApi import com.github.avrokotlin.avro4k.InternalAvro4kApi @@ -18,24 +19,28 @@ import ns2.Enum @Serializable @AvroGenerated("""[{"type":"record","name":"Record","namespace":"ns","fields":[{"name":"field","type":"int"}]},{"type":"record","name":"RecordWithoutNamespace","fields":[{"name":"field","type":"int"}]},{"type":"enum","name":"Enum","namespace":"ns2","symbols":["FIRST","SECOND"]},{"type":"fixed","name":"FixedType","size":12},"string","bytes"]""") public sealed interface Union { + @AvroAlias("ns.Record") @JvmInline @Serializable public value class ForRecord( public val `value`: Record, ) : Union + @AvroAlias("RecordWithoutNamespace") @JvmInline @Serializable public value class ForRecordWithoutNamespace( public val `value`: RecordWithoutNamespace, ) : Union + @AvroAlias("ns2.Enum") @JvmInline @Serializable public value class ForEnum( public val `value`: Enum, ) : Union + @AvroAlias("FixedType") @JvmInline @Serializable public value class ForFixedType( @@ -43,12 +48,14 @@ public sealed interface Union { public val `value`: ByteArray, ) : Union + @AvroAlias("string") @JvmInline @Serializable public value class ForString( public val `value`: String, ) : Union + @AvroAlias("bytes") @JvmInline @Serializable public value class ForBytes(