avro-kotlin / avro4k

Avro format support for Kotlin
Apache License 2.0
197 stars 37 forks source link

Enum decoding does not respect default values #122

Closed cgadski closed 2 years ago

cgadski commented 3 years ago

Problem

According to the avro documentation, when an enum type has a default value, out-of-range enum values will be decoded to the default. However, Avro.default.decodeFromByteArray does not respect this behavior; instead, when decoding an enum that takes an unknown value, it always throws an exception.

Minimum Working Example

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroEnumDefault
import kotlinx.serialization.Serializable

@Serializable
@AvroEnumDefault("UNKNOWN")
enum class EnumWithDefault {
    UNKNOWN, A, B
}

@Serializable
@AvroEnumDefault("UNKNOWN")
enum class FutureEnumWithDefault {
    UNKNOWN, A, B, C
}

@Serializable
data class Wrap(val value: EnumWithDefault)

@Serializable
data class FutureWrap(val value: FutureEnumWithDefault)

fun fromFuture(v: FutureEnumWithDefault): EnumWithDefault {
    val encoded = Avro.default.encodeToByteArray(FutureWrap.serializer(), FutureWrap(v))
    val decoded: Wrap = Avro.default.decodeFromByteArray(Wrap.serializer(), encoded)
    return decoded.value
}

fun main() {
    assert(fromFuture(FutureEnumWithDefault.C) == EnumWithDefault.UNKNOWN) // this fails
}

I expect the assertion to pass, but it fails with the following exception:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
        at com.example.EnumWithDefault$$serializer.deserialize(Main.kt:7)
        at com.example.EnumWithDefault$$serializer.deserialize(Main.kt:7)
        at kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:260)
        at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
        at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:43)
        at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableElement(AbstractDecoder.kt:70)
        at com.example.Wrap$$serializer.deserialize(Main.kt:19)
        at com.example.Wrap$$serializer.deserialize(Main.kt:19)
        at kotlinx.serialization.encoding.Decoder$DefaultImpls.decodeSerializableValue(Decoding.kt:260)
        at kotlinx.serialization.encoding.AbstractDecoder.decodeSerializableValue(AbstractDecoder.kt:16)
        at com.github.avrokotlin.avro4k.Avro.fromRecord(Avro.kt:252)
        at com.github.avrokotlin.avro4k.Avro$openInputStream$builder$2.invoke(Avro.kt:179)
        at com.github.avrokotlin.avro4k.io.AvroDataInputStream.next(AvroDataInputStream.kt:27)
        at com.github.avrokotlin.avro4k.io.AvroInputStream$DefaultImpls.nextOrThrow(AvroInputStream.kt:25)
        at com.github.avrokotlin.avro4k.io.AvroDataInputStream.nextOrThrow(AvroDataInputStream.kt:8)
        at com.github.avrokotlin.avro4k.Avro.decodeFromByteArray(Avro.kt:145)
        at com.example.MainKt.fromFuture(Main.kt:27)
        at com.example.MainKt.main(Main.kt:35)
        at com.example.MainKt.main(Main.kt)
kossi commented 2 years ago

Hi @cgadski. I opened PR #123 that should fix this and also that other issue related to namespace name overriding. Thanks for providing those examples it was easy to make tests against those.

Feel free to pull that PR from my fork and test it if there is something still missing. I think I added something related to enum defaults feature some time ago and have used it and avro4k with one project but luckily this have not been an issue. Or haven't really realised what the actual implementation by defined the avro documentation should be. Thanks for noticing this

thake commented 2 years ago

@cgadski thanks for raising this issue. The fix provided by @kossi has been merged. Before building a new release, I need to fix another issue regarding sealed interfaces.