Repository: avro-kotlin/avro4k (kotlin)

Issue:
Decoding fails when missing map in reader class
**Describe the bug**
In RecordDirectDecoder.kt, the code incorrectly uses `schema.elementType` instead of `schema.valueType` when handling a map type from Avro. Since an Avro map contains values (not elements like arrays), this causes the skip operation to fail, leading to the error in the stack trace section

I encountered this error after evolving a schema in the producer and Schema Registry, adding a new field of type map (with empty default value)

The code from RecordDirectDecoder.kd, where the bug is placed:
```
Schema.Type.MAP -> {
    var mapBlockItems: Long = skipMap()
    while (mapBlockItems > 0L) {
        for (i in 0L until mapBlockItems) {
            skipString()
            skip(schema.elementType)
        }
        mapBlockItems = skipMap()
    }
}
```

**Stack trace**
```
Caused by: org.apache.avro.AvroRuntimeException: Not an array: {"type":"map","values" ... not pasting the full schema }
	at org.apache.avro.Schema.getElementType(Schema.java:374)
	at com.github.avrokotlin.avro4k.internal.decoder.direct.RecordDirectDecoderKt.skip(RecordDirectDecoder.kt:204)
	at com.github.avrokotlin.avro4k.internal.decoder.direct.RecordDirectDecoderKt.access$skip(RecordDirectDecoder.kt:1)
	at com.github.avrokotlin.avro4k.internal.decoder.direct.RecordDirectDecoder.decodeElementIndex(RecordDirectDecoder.kt:42)
```

**To Reproduce**
I could not replicate the behavior in unit tests yet because mapBlockItems is always equal to zero when I try, however, I can outline the steps that led to the failure in all the environments I tested the library:
- Evolve an Avro schema in the producer and Schema Registry by adding a new map field, using an empty map `{}` as the default value.
- Produce a message using the evolved schema.
- Same error should be triggered

If I manage to reproduce it in a test, I'll add an update in a new comment


**Expected behavior**
The map type is skipped correctly 

**Desktop:**
- Kotlin version: 2.1.10
- Avro4k version: 2.2.0
- Other kotlinx-version: 1.8.0

Pull request:
Closes #296

Patch:
diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/internal/decoder/direct/RecordDirectDecoder.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/internal/decoder/direct/RecordDirectDecoder.kt
index 13bf2662..e6c6d705 100644
--- a/src/main/kotlin/com/github/avrokotlin/avro4k/internal/decoder/direct/RecordDirectDecoder.kt
+++ b/src/main/kotlin/com/github/avrokotlin/avro4k/internal/decoder/direct/RecordDirectDecoder.kt
@@ -201,7 +201,7 @@ private fun Decoder.skip(s: Schema) {
             while (mapBlockItems > 0L) {
                 for (i in 0L until mapBlockItems) {
                     skipString()
-                    skip(schema.elementType)
+                    skip(schema.valueType)
                 }
                 mapBlockItems = skipMap()
             }
@@ -214,6 +214,7 @@ private fun Decoder.skip(s: Schema) {
             }
         }
 
-        else -> throw SerializationException("Unsupported schema type for $schema")
+        // Impossible to go to this branch, as the schema is resolved earlier in this method
+        Schema.Type.UNION -> throw UnsupportedOperationException("Union type should be already resolved")
     }
 }
\ No newline at end of file


Test patch:
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordDecodingCompatibilityTests.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordDecodingCompatibilityTests.kt
new file mode 100644
index 00000000..57795c6d
--- /dev/null
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordDecodingCompatibilityTests.kt
@@ -0,0 +1,121 @@
+package com.github.avrokotlin.avro4k.encoding
+
+import com.github.avrokotlin.avro4k.Avro
+import com.github.avrokotlin.avro4k.AvroAlias
+import com.github.avrokotlin.avro4k.decodeFromByteArray
+import com.github.avrokotlin.avro4k.encodeToByteArray
+import com.github.avrokotlin.avro4k.schema
+import io.kotest.core.spec.style.FunSpec
+import io.kotest.matchers.shouldBe
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+import org.apache.avro.Schema
+import org.apache.avro.SchemaBuilder
+
+class RecordDecodingCompatibilityTests : FunSpec() {
+    init {
+        test("should skip written boolean when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.BOOLEAN),
+                true
+            )
+        }
+        test("should skip written int when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.INT),
+                42
+            )
+        }
+        test("should skip written long when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.LONG),
+                17L
+            )
+        }
+        test("should skip written float when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.FLOAT),
+                3.14f
+            )
+        }
+        test("should skip written double when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.DOUBLE),
+                21.9
+            )
+        }
+        test("should skip written string when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.STRING),
+                "text"
+            )
+        }
+        test("should skip written bytes when absent from reading data class") {
+            testCompatibility(
+                Schema.create(Schema.Type.BYTES),
+                byteArrayOf(1, 2, 3)
+            )
+        }
+        test("should skip written fixed when absent from reading data class") {
+            testCompatibility(
+                Schema.createFixed("FixedName", null, null, 3),
+                byteArrayOf(1, 2, 3)
+            )
+        }
+        test("should skip written enum when absent from reading data class") {
+            testCompatibility(
+                Schema.createEnum("EnumName", null, null, listOf("A", "B")),
+                "B"
+            )
+        }
+        test("should skip written array when absent from reading data class") {
+            testCompatibility(
+                Schema.createArray(Schema.create(Schema.Type.INT)),
+                listOf(4, 2)
+            )
+        }
+        test("should skip written map when absent from reading data class") {
+            testCompatibility(
+                Schema.createMap(Schema.create(Schema.Type.STRING)),
+                mapOf("key" to "value")
+            )
+        }
+        test("should skip written null when absent from reading data class") {
+            testCompatibility<Int?>(
+                SchemaBuilder.nullable().intType(),
+                null
+            )
+        }
+        test("should skip written record when absent from reading data class") {
+            testCompatibility<ReadDataClass>(
+                Avro.schema<ReadDataClass>(),
+                ReadDataClass("test", 42)
+            )
+        }
+    }
+
+    private inline fun <reified T> testCompatibility(
+        skippedSchema: Schema,
+        writtenData: T,
+    ) {
+        val schema =
+            SchemaBuilder.record("WrittenDataClass")
+                .fields()
+                .name("firstField").type().stringType().noDefault()
+                .name("skippedField").type(skippedSchema).noDefault()
+                .name("secondField").type().intType().noDefault()
+                .endRecord()
+        val encodedBytes = Avro.encodeToByteArray(schema, WrittenDataClass("test", writtenData, 42))
+        val decodedData = Avro.decodeFromByteArray<ReadDataClass>(schema, encodedBytes)
+
+        decodedData shouldBe ReadDataClass("test", 42)
+    }
+
+    @Serializable
+    @SerialName("WrittenDataClass")
+    private data class WrittenDataClass<T>(val firstField: String, val skippedField: T, val secondField: Int)
+
+    @Serializable
+    @AvroAlias("WrittenDataClass")
+    private data class ReadDataClass(val firstField: String, val secondField: Int)
+}
\ No newline at end of file
diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordEncodingTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordEncodingTest.kt
index d80241f4..79741e7f 100644
--- a/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordEncodingTest.kt
+++ b/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/RecordEncodingTest.kt
@@ -164,50 +164,6 @@ internal class RecordEncodingTest : StringSpec({
                 writerSchema = differentWriterSchema
             )
     }
-    "support written records where some fields are missing from kotlin data class when decoding" {
-        @Serializable
-        @SerialName("Foo")
-        data class MissingFields(
-            val c: Boolean?,
-        )
-
-        val input =
-            Foo(
-                "string value",
-                2.2,
-                true,
-                S(setOf(1, 2, 3)),
-                vc = ValueClass(ByteArray(3) { it.toByte() })
-            )
-
-        AvroAssertions.assertThat(input)
-            .isDecodedAs(MissingFields(true))
-    }
-    "support decoding from a writer schema with missing descriptor fields (just skipping, no reordering)" {
-        @Serializable
-        @SerialName("TheClass")
-        data class TheClass(
-            val a: String?,
-            val b: Boolean?,
-            val c: Int,
-        )
-
-        @Serializable
-        @SerialName("TheClass")
-        data class TheLightClass(
-            val b: Boolean?,
-        )
-
-        val writerSchema =
-            SchemaBuilder.record("TheClass").fields()
-                .name("a").type(Schema.create(Schema.Type.STRING).nullable).withDefault(null)
-                .name("b").type().booleanType().noDefault()
-                .name("c").type().intType().intDefault(42)
-                .endRecord()
-
-        AvroAssertions.assertThat(TheClass("hello", true, 42))
-            .isDecodedAs(TheLightClass(true), writerSchema = writerSchema)
-    }
     "support encoding & decoding with additional descriptor optional fields (no reordering)" {
         @Serializable
         @SerialName("TheClass")