Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize encoding/decoding tests #168

Merged
merged 6 commits into from
Jan 28, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.modules.SerializersModule
import org.apache.avro.Schema
import org.apache.avro.generic.GenericFixed
import org.apache.avro.generic.GenericRecord
import java.nio.ByteBuffer

Expand Down Expand Up @@ -60,6 +61,7 @@ class RecordDecoder(
is Array<*> -> ByteArrayDecoder((value as Array<Byte>).toByteArray(), serializersModule)
is ByteArray -> ByteArrayDecoder(value, serializersModule)
is ByteBuffer -> ByteArrayDecoder(value.array(), serializersModule)
is GenericFixed -> ByteArrayDecoder(value.bytes(), serializersModule)
else -> this
}
} else {
Expand Down Expand Up @@ -153,6 +155,15 @@ class RecordDecoder(
}
}

override fun decodeShort(): Short {
return when (val v = fieldValue()) {
is Short -> v
is Int -> v.toShort()
null -> throw SerializationException("Cannot decode <null> as a Short")
else -> throw SerializationException("Unsupported type for Short ${v.javaClass}")
}
}

override fun decodeLong(): Long {
return when (val v = fieldValue()) {
is Long -> v
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.github.avrokotlin.avro4k

import org.apache.avro.Schema
import org.apache.avro.generic.GenericData
import org.apache.avro.generic.GenericRecord

data class RecordBuilderForTest(
val fields: List<Any?>
) {
fun createRecord(schema: Schema): GenericRecord {
val record = GenericData.Record(schema)
fields.forEachIndexed { index, value ->
val fieldSchema = schema.fields[index].schema()
record.put(index, convertValue(value, fieldSchema))
}
return record
}

private fun convertValue(
value: Any?,
schema: Schema
): Any? {
return when (value) {
is RecordBuilderForTest -> value.createRecord(schema)
is Map<*, *> -> createMap(schema, value)
is List<*> -> createList(schema, value)
else -> value
}
}

fun createList(schema: Schema, value: List<*>): List<*> {
val valueSchema = schema.elementType
return value.map { convertValue(it, valueSchema) }
}

fun <K, V> createMap(schema: Schema, value: Map<K, V>): Map<K, *> {
val valueSchema = schema.valueType
return value.mapValues { convertValue(it.value, valueSchema) }
}
}

fun record(vararg fields: Any?): RecordBuilderForTest {
return RecordBuilderForTest(listOf(*fields))
}
246 changes: 85 additions & 161 deletions src/test/kotlin/com/github/avrokotlin/avro4k/decoder/ArrayDecoderTest.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.github.avrokotlin.avro4k.decoder

import com.github.avrokotlin.avro4k.Avro
import io.kotest.matchers.shouldBe
import io.kotest.core.spec.style.WordSpec
import io.kotest.matchers.shouldBe
import kotlinx.serialization.Serializable
import org.apache.avro.Schema
import org.apache.avro.generic.GenericData
Expand Down Expand Up @@ -30,166 +30,90 @@ data class Record(val str: String, val double: Double)

class ArrayDecoderTest : WordSpec({

"Decoder" should {

"support array for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans", arrayOf(true, false, true))
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe
listOf(true, false, true)
}

"support list for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans", listOf(true, false, true))
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe
listOf(true, false, true)
}

"support GenericData.Array for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.BOOLEAN)), listOf(true, false, true)))
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe
listOf(true, false, true)
}

"support array for a List of doubles" {

val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles", arrayOf(12.54, 23.5, 9123.2314))
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe
TestListDoubles(listOf(12.54, 23.5, 9123.2314))
}

"support list for a List of doubles" {

val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles", listOf(12.54, 23.5, 9123.2314))
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe
TestListDoubles(listOf(12.54, 23.5, 9123.2314))
}

"support GenericData.Array for a List of doubles" {

val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.DOUBLE)), listOf(12.54, 23.5, 9123.2314)))
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe
TestListDoubles(listOf(12.54, 23.5, 9123.2314))
}

"support array for a List of records" {

val containerSchema = Avro.default.schema(TestListRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records", arrayOf(record1, record2))

Avro.default.fromRecord(TestListRecords.serializer(), container) shouldBe
TestListRecords(listOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support list for a List of records" {

val containerSchema = Avro.default.schema(TestListRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records", listOf(record1, record2))

Avro.default.fromRecord(TestListRecords.serializer(), container) shouldBe
TestListRecords(listOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support array for a Set of records" {

val containerSchema = Avro.default.schema(TestSetRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records", arrayOf(record1, record2))

Avro.default.fromRecord(TestSetRecords.serializer(), container) shouldBe
TestSetRecords(setOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support GenericData.Array for a Set of records" {

val containerSchema = Avro.default.schema(TestSetRecords.serializer())
val recordSchema = Avro.default.schema(Record.serializer())

val record1 = GenericData.Record(recordSchema)
record1.put("str", "qwe")
record1.put("double", 123.4)

val record2 = GenericData.Record(recordSchema)
record2.put("str", "wer")
record2.put("double", 8234.324)

val container = GenericData.Record(containerSchema)
container.put("records",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.STRING)), listOf(record1, record2)))

Avro.default.fromRecord(TestSetRecords.serializer(), container) shouldBe
TestSetRecords(setOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}

"support array for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings", arrayOf("Qwe", "324", "q"))
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe
TestSetString(setOf("Qwe", "324", "q"))
}

"support list for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings", arrayOf("Qwe", "324", "q"))
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe
TestSetString(setOf("Qwe", "324", "q"))
}

"support GenericData.Array for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings",
GenericData.Array(Schema.createArray(Schema.create(Schema.Type.STRING)), listOf("Qwe", "324", "q")))
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe
TestSetString(setOf("Qwe", "324", "q"))
"Decoder" should {
listOf(
"array" to arrayOf(true, false, true),
"list" to listOf(true, false, true),
"GenericData.Array" to GenericData.Array(
Schema.createArray(Schema.create(Schema.Type.BOOLEAN)), listOf(true, false, true)
)
).forEach {
"support ${it.first} for an Array of booleans" {
val schema = Avro.default.schema(TestArrayBooleans.serializer())
val record = GenericData.Record(schema)
record.put("booleans", it.second)
Avro.default.fromRecord(TestArrayBooleans.serializer(), record).booleans.toList() shouldBe listOf(
true, false, true
)
}
}
listOf(
"array" to arrayOf(12.54, 23.5, 9123.2314),
"list" to listOf(12.54, 23.5, 9123.2314),
"GenericData.Array" to GenericData.Array(
Schema.createArray(Schema.create(Schema.Type.DOUBLE)), listOf(12.54, 23.5, 9123.2314)
)
).forEach {
"support ${it.first} for a List of doubles" {
val schema = Avro.default.schema(TestListDoubles.serializer())
val record = GenericData.Record(schema)
record.put("doubles", it.second)
Avro.default.fromRecord(TestListDoubles.serializer(), record) shouldBe TestListDoubles(
listOf(
12.54, 23.5, 9123.2314
)
)
}
}
val recordSchema = Avro.default.schema(Record.serializer())
val records = listOf(GenericData.Record(recordSchema).apply {
put("str", "qwe")
put("double", 123.4)
}, GenericData.Record(recordSchema).apply {
put("str", "wer")
put("double", 8234.324)
})
listOf(
"array" to records.toTypedArray(),
"list" to records,
"GenericData.Array" to GenericData.Array(
Schema.createArray(recordSchema), records
)
).forEach {
"support ${it.first} for a List of records" {
val containerSchema = Avro.default.schema(TestListRecords.serializer())
val container = GenericData.Record(containerSchema)
container.put("records", it.second)

Avro.default.fromRecord(
TestListRecords.serializer(), container
) shouldBe TestListRecords(listOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}
"support ${it.first} for a Set of records" {
val containerSchema = Avro.default.schema(TestSetRecords.serializer())
val container = GenericData.Record(containerSchema)
container.put("records", it.second)

Avro.default.fromRecord(
TestSetRecords.serializer(), container
) shouldBe TestSetRecords(setOf(Record("qwe", 123.4), Record("wer", 8234.324)))
}
}

listOf(
"array" to arrayOf("Qwe", "324", "q"),
"list" to listOf("Qwe", "324", "q"),
"GenericData.Array" to GenericData.Array(
Schema.createArray(Schema.create(Schema.Type.STRING)), listOf("Qwe", "324", "q")
)
).forEach {
"support ${it.first} for a Set of strings" {
val schema = Avro.default.schema(TestSetString.serializer())
val record = GenericData.Record(schema)
record.put("strings", it.second)
Avro.default.fromRecord(TestSetString.serializer(), record) shouldBe TestSetString(setOf("Qwe", "324", "q"))
}
}
}
}

})
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.github.avrokotlin.avro4k.decoder

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroDefault
import com.github.avrokotlin.avro4k.AvroName
import com.github.avrokotlin.avro4k.ScalePrecision
import com.github.avrokotlin.avro4k.*
import com.github.avrokotlin.avro4k.io.AvroDecodeFormat
import com.github.avrokotlin.avro4k.serializer.BigDecimalSerializer
import io.kotest.core.spec.style.FunSpec
Expand Down Expand Up @@ -51,6 +48,23 @@ data class ContainerWithDefaultFields(
@Serializable(BigDecimalSerializer::class)
val bigDecimal : BigDecimal
)
@Serializable
@AvroEnumDefault("UNKNOWN")
enum class EnumWithDefault {
UNKNOWN, A
}

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

@Serializable
data class Wrap(val value: EnumWithDefault)

@Serializable
data class FutureWrap(val value: FutureEnumWithDefault)

class AvroDefaultValuesDecoderTest : FunSpec({
test("test default values correctly decoded") {
Expand All @@ -73,5 +87,15 @@ class AvroDefaultValuesDecoderTest : FunSpec({
deserialized.emptyFooList.shouldBeEmpty()
deserialized.filledFooList.shouldContainExactly(FooElement("bar"))
deserialized.bigDecimal.shouldBe(BigDecimal.ZERO)

}
test("Decoding enum with an unknown or future value uses default value") {
val encoded = Avro.default.encodeToByteArray(
FutureWrap.serializer(),
FutureWrap(FutureEnumWithDefault.C)
)
val decoded = Avro.default.decodeFromByteArray(Wrap.serializer(), encoded)

decoded shouldBe Wrap(EnumWithDefault.UNKNOWN)
}
})
Loading