diff --git a/api/avro4k-core.api b/api/avro4k-core.api index ccabebb..95f199d 100644 --- a/api/avro4k-core.api +++ b/api/avro4k-core.api @@ -414,6 +414,15 @@ public final class com/github/avrokotlin/avro4k/serializer/BigIntegerSerializer public fun serializeGeneric (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigInteger;)V } +public final class com/github/avrokotlin/avro4k/serializer/ByteBufferSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer { + public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/ByteBufferSerializer; + public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object; + public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/nio/ByteBuffer; + public fun getSchema (Lcom/github/avrokotlin/avro4k/serializer/SchemaSupplierContext;)Lorg/apache/avro/Schema; + public synthetic fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/lang/Object;)V + public fun serializeAvro (Lcom/github/avrokotlin/avro4k/AvroEncoder;Ljava/nio/ByteBuffer;)V +} + public final class com/github/avrokotlin/avro4k/serializer/ElementLocation { public fun (Lkotlinx/serialization/descriptors/SerialDescriptor;I)V public final fun component1 ()Lkotlinx/serialization/descriptors/SerialDescriptor; diff --git a/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/JavaStdLibSerializers.kt b/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/JavaStdLibSerializers.kt index 5eea0d1..92c3f20 100644 --- a/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/JavaStdLibSerializers.kt +++ b/src/main/kotlin/com/github/avrokotlin/avro4k/serializer/JavaStdLibSerializers.kt @@ -34,6 +34,7 @@ public val JavaStdLibSerializersModule: SerializersModule = contextual(UUIDSerializer) contextual(BigIntegerSerializer) contextual(BigDecimalSerializer) + contextual(ByteBufferSerializer) } public object URLSerializer : KSerializer { @@ -315,4 +316,31 @@ public object BigDecimalSerializer : AvroSerializer(BigDecimal::clas get() { return LogicalTypes.decimal(precision, scale) } +} + +/** + * Delegates the serialization of a [ByteBuffer] to [AvroEncoder.encodeBytes] and [AvroDecoder.decodeBytes] as all the compatible types + * for the current writer schema are specifically handled here. + */ +public object ByteBufferSerializer : AvroSerializer(ByteBuffer::class.qualifiedName!!) { + override fun getSchema(context: SchemaSupplierContext): Schema { + return Schema.create(Schema.Type.BYTES) + } + + override fun serializeAvro( + encoder: AvroEncoder, + value: ByteBuffer, + ) { + if (value.hasArray() && value.arrayOffset() == 0 && value.array().size == value.remaining()) { + encoder.encodeBytes(value.array()) + } else { + val bytes = ByteArray(value.remaining()) + value.get(bytes) + encoder.encodeBytes(bytes) + } + } + + override fun deserializeAvro(decoder: AvroDecoder): ByteBuffer { + return ByteBuffer.wrap(decoder.decodeBytes()) + } } \ No newline at end of file diff --git a/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/BytesEncodingTest.kt b/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/BytesEncodingTest.kt index 8b0d8c4..3e6145e 100644 --- a/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/BytesEncodingTest.kt +++ b/src/test/kotlin/com/github/avrokotlin/avro4k/encoding/BytesEncodingTest.kt @@ -6,8 +6,41 @@ import com.github.avrokotlin.avro4k.record import io.kotest.core.spec.style.StringSpec import kotlinx.serialization.Serializable import org.apache.avro.Schema +import java.nio.ByteBuffer internal class BytesEncodingTest : StringSpec({ + "support ByteBuffer as BYTES" { + AvroAssertions.assertThat(ByteBuffer.wrap(byteArrayOf(1, 4, 9))) + .generatesSchema(Schema.create(Schema.Type.BYTES)) + .isEncodedAs(byteArrayOf(1, 4, 9)) + AvroAssertions.assertThat(ByteBuffer.wrap(byteArrayOf(1, 4, 9))) + .generatesSchema(Schema.create(Schema.Type.BYTES).nullable) + .isEncodedAs(byteArrayOf(1, 4, 9)) + AvroAssertions.assertThat(null) + .generatesSchema(Schema.create(Schema.Type.BYTES).nullable) + .isEncodedAs(null) + } + + "support ByteBuffer as FIXED" { + val fixedSchema = Schema.createFixed("fixed", null, null, 3) + AvroAssertions.assertThat(ByteBuffer.wrap(byteArrayOf(1, 4, 9))) + .isEncodedAs(byteArrayOf(1, 4, 9), writerSchema = fixedSchema) + AvroAssertions.assertThat(ByteBuffer.wrap(byteArrayOf(1, 4, 9))) + .isEncodedAs(byteArrayOf(1, 4, 9), writerSchema = fixedSchema.nullable) + AvroAssertions.assertThat(null) + .isEncodedAs(null, writerSchema = fixedSchema.nullable) + } + + "support ByteBuffer as STRING" { + val stringSchema = Schema.create(Schema.Type.STRING) + AvroAssertions.assertThat(ByteBuffer.wrap("string".encodeToByteArray())) + .isEncodedAs("string", writerSchema = stringSchema) + AvroAssertions.assertThat(ByteBuffer.wrap("string".encodeToByteArray())) + .isEncodedAs("string", writerSchema = stringSchema.nullable) + AvroAssertions.assertThat(null) + .isEncodedAs(null, writerSchema = stringSchema.nullable) + } + "encode/decode nullable ByteArray to BYTES" { AvroAssertions.assertThat(NullableByteArrayTest(byteArrayOf(1, 4, 9))) .isEncodedAs(record(byteArrayOf(1, 4, 9)))