Skip to content

Commit

Permalink
Merge pull request #238 from Chuckame/feat/decimal-default
Browse files Browse the repository at this point in the history
feat: Remove AvroDecimal defaults
  • Loading branch information
Chuckame authored Jul 11, 2024
2 parents b0aec20 + eb37957 commit eda4375
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 124 deletions.
57 changes: 56 additions & 1 deletion Migrating-from-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,59 @@ data class TheDataClass(
val listOfBytes: ByteArray,
val setOfBytes: ByteArray,
)
```
```

## Serialize a `BigDecimal` as a string

> [!INFO]
> Note that you can replace `@Serializable(with = BigDecimalAsStringSerializer::class)` with `@Contextual` to use the default global `BigDecimalSerializer` already registered,
> which is already compatible with the `@AvroStringable` feature.
```kotlin
// Previously
@Serializable
data class MyData(
@Serializable(with = BigDecimalAsStringSerializer::class)
val bigDecimalAsString: BigDecimal,
)

// Now
@Serializable
data class MyData(
@Contextual
@AvroStringable
val bigDecimalAsString: BigDecimal,
)
```

## Serialize a `BigDecimal` as a BYTES or FIXED

Previously, a BigDecimal was serialized as bytes with 2 as scale and 8 as precision. Now you have to explicitly declare the needed scale and precision using `@AvroDecimal`,
or use `@AvroStringable` to serialize it as a string which doesn't need scale nor precision.

> [!INFO]
> Note that you can replace `@Serializable(with = BigDecimalSerializer::class)` with `@Contextual` to use the default global `BigDecimalSerializer` already registered.
```kotlin
// Previously
@Serializable
data class MyData(
@Serializable(with = BigDecimalSerializer::class)
val bigDecimalAsBytes: BigDecimal,
@AvroFixed(10)
@Serializable(with = BigDecimalSerializer::class)
val bigDecimalAsFixed: BigDecimal,
)

// Now
@Serializable
data class MyData(
@Contextual
@AvroDecimal(scale = 8, precision = 2)
val bigDecimalAsBytes: BigDecimal,
@Contextual
@AvroFixed(10)
@AvroDecimal(scale = 8, precision = 2)
val bigDecimalAsFixed: BigDecimal,
)
```
58 changes: 29 additions & 29 deletions README.md

Large diffs are not rendered by default.

14 changes: 0 additions & 14 deletions api/avro4k-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ public abstract interface annotation class com/github/avrokotlin/avro4k/AvroDeci

public synthetic class com/github/avrokotlin/avro4k/AvroDecimal$Impl : com/github/avrokotlin/avro4k/AvroDecimal {
public fun <init> (II)V
public synthetic fun <init> (IIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final synthetic fun precision ()I
public final synthetic fun scale ()I
}
Expand Down Expand Up @@ -375,19 +374,6 @@ public final class com/github/avrokotlin/avro4k/serializer/AvroSerializerKt {
public static final fun getStringable (Lcom/github/avrokotlin/avro4k/serializer/ElementLocation;)Lcom/github/avrokotlin/avro4k/AvroStringable;
}

public final class com/github/avrokotlin/avro4k/serializer/BigDecimalAsStringSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/BigDecimalAsStringSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
public fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/math/BigDecimal;
public synthetic fun deserializeGeneric (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserializeGeneric (Lkotlinx/serialization/encoding/Decoder;)Ljava/math/BigDecimal;
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/math/BigDecimal;)V
public synthetic fun serializeGeneric (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serializeGeneric (Lkotlinx/serialization/encoding/Encoder;Ljava/math/BigDecimal;)V
}

public final class com/github/avrokotlin/avro4k/serializer/BigDecimalSerializer : com/github/avrokotlin/avro4k/serializer/AvroSerializer {
public static final field INSTANCE Lcom/github/avrokotlin/avro4k/serializer/BigDecimalSerializer;
public synthetic fun deserializeAvro (Lcom/github/avrokotlin/avro4k/AvroDecoder;)Ljava/lang/Object;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.github.avrokotlin.avro4k.serializer.BigDecimalAsStringSerializer
import com.github.avrokotlin.avro4k.AvroStringable
import com.github.avrokotlin.avro4k.serializer.InstantSerializer
import com.github.avrokotlin.avro4k.serializer.LocalDateSerializer
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import java.math.BigDecimal
import java.time.Instant
Expand Down Expand Up @@ -44,7 +45,8 @@ internal data class Client(
val id: Long,
val index: Int,
val isActive: Boolean,
@Serializable(with = BigDecimalAsStringSerializer::class)
@Contextual
@AvroStringable
@JsonFormat(shape = JsonFormat.Shape.STRING)
val balance: BigDecimal?,
val picture: ByteArray?,
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/com/github/avrokotlin/avro4k/Annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ public annotation class AvroStringable
@ExperimentalSerializationApi
@Target(AnnotationTarget.PROPERTY)
public annotation class AvroDecimal(
val scale: Int = 2,
val precision: Int = 8,
val scale: Int,
val precision: Int,
)

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.github.avrokotlin.avro4k.AvroDecoder
import com.github.avrokotlin.avro4k.AvroEncoder
import com.github.avrokotlin.avro4k.decodeResolvingAny
import com.github.avrokotlin.avro4k.encodeResolving
import com.github.avrokotlin.avro4k.internal.AvroSchemaGenerationException
import com.github.avrokotlin.avro4k.internal.BadEncodedValueError
import com.github.avrokotlin.avro4k.internal.UnexpectedDecodeSchemaError
import com.github.avrokotlin.avro4k.internal.copy
Expand Down Expand Up @@ -180,15 +181,21 @@ public object BigIntegerSerializer : AvroSerializer<BigInteger>(BigInteger::clas
}
}

private val converter = Conversions.DecimalConversion()
private val defaultAnnotation = AvroDecimal()

public object BigDecimalSerializer : AvroSerializer<BigDecimal>(BigDecimal::class.qualifiedName!!) {
private val converter = Conversions.DecimalConversion()

override fun getSchema(context: SchemaSupplierContext): Schema {
val logicalType = (context.inlinedElements.firstNotNullOfOrNull { it.decimal } ?: defaultAnnotation).logicalType
val logicalType = context.inlinedElements.firstNotNullOfOrNull { it.decimal }?.logicalType

fun nonNullLogicalType(): LogicalTypes.Decimal {
if (logicalType == null) {
throw AvroSchemaGenerationException("BigDecimal requires @${AvroDecimal::class.qualifiedName} to works with 'fixed' or 'bytes' schema types.")
}
return logicalType
}
return context.inlinedElements.firstNotNullOfOrNull {
it.stringable?.createSchema() ?: it.fixed?.createSchema(it)?.copy(logicalType = logicalType)
} ?: Schema.create(Schema.Type.BYTES).copy(logicalType = logicalType)
it.stringable?.createSchema() ?: it.fixed?.createSchema(it)?.copy(logicalType = nonNullLogicalType())
} ?: Schema.create(Schema.Type.BYTES).copy(logicalType = nonNullLogicalType())
}

override fun serializeAvro(
Expand Down Expand Up @@ -308,32 +315,4 @@ public object BigDecimalSerializer : AvroSerializer<BigDecimal>(BigDecimal::clas
get() {
return LogicalTypes.decimal(precision, scale)
}
}

public object BigDecimalAsStringSerializer : AvroSerializer<BigDecimal>(BigDecimal::class.qualifiedName!!) {
override fun getSchema(context: SchemaSupplierContext): Schema {
return Schema.create(Schema.Type.STRING)
}

override fun serializeAvro(
encoder: AvroEncoder,
value: BigDecimal,
) {
BigDecimalSerializer.serializeAvro(encoder, value)
}

override fun serializeGeneric(
encoder: Encoder,
value: BigDecimal,
) {
encoder.encodeString(value.toString())
}

override fun deserializeAvro(decoder: AvroDecoder): BigDecimal {
return BigDecimalSerializer.deserializeAvro(decoder)
}

override fun deserializeGeneric(decoder: Decoder): BigDecimal {
return decoder.decodeString().toBigDecimal()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,28 +93,30 @@ internal class AvroDefaultEncodingTest : StringSpec({
@AvroDefault("""[{"content":"bar"}]""")
val filledFooList: List<FooElement>,
@Contextual
@AvroDecimal(scale = 0)
@AvroDecimal(scale = 0, precision = 8)
@AvroDefault("\u0000")
val bigDecimal: BigDecimal,
@Contextual
@AvroDecimal(scale = 0)
@AvroDecimal(scale = 0, precision = 8)
@AvroDefault("\u0000")
val bigDecimalNullable: BigDecimal?,
@Contextual
@AvroDecimal(scale = 2, precision = 8)
@AvroDefault("null")
val bigDecimalNullableNull: BigDecimal?,
@Contextual
@AvroFixed(size = 16)
@AvroDecimal(scale = 0)
@AvroDecimal(scale = 0, precision = 8)
@AvroDefault("\u0000")
val bigDecimalFixed: BigDecimal,
@Contextual
@AvroFixed(size = 16)
@AvroDecimal(scale = 0)
@AvroDecimal(scale = 0, precision = 8)
@AvroDefault("\u0000")
val bigDecimalFixedNullable: BigDecimal?,
@Contextual
@AvroFixed(size = 16)
@AvroDecimal(scale = 2, precision = 8)
@AvroDefault("null")
val bigDecimalFixedNullableNull: BigDecimal?,
val kotlinDefault: Int = 42,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ internal class BytesEncodingTest : StringSpec({
AvroAssertions.assertThat(NullableByteArrayTest(null))
.isEncodedAs(record(null))

AvroAssertions.assertThat<ByteArray?>()
AvroAssertions.assertThat<ByteArray?>(byteArrayOf(1, 4, 9))
.generatesSchema(Schema.create(Schema.Type.BYTES).nullable)
AvroAssertions.assertThat(byteArrayOf(1, 4, 9))
.isEncodedAs(byteArrayOf(1, 4, 9))
AvroAssertions.assertThat<ByteArray?>(null)
.generatesSchema(Schema.create(Schema.Type.BYTES).nullable)
.isEncodedAs(null)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package com.github.avrokotlin.avro4k.encoding

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroAssertions
import com.github.avrokotlin.avro4k.AvroDecimal
import com.github.avrokotlin.avro4k.AvroFixed
import com.github.avrokotlin.avro4k.AvroStringable
import com.github.avrokotlin.avro4k.record
import com.github.avrokotlin.avro4k.schema
import com.github.avrokotlin.avro4k.serializer.BigDecimalAsStringSerializer
import com.github.avrokotlin.avro4k.serializer.InstantToMicroSerializer
import io.kotest.core.spec.style.StringSpec
import kotlinx.serialization.Contextual
Expand Down Expand Up @@ -59,25 +60,6 @@ internal class LogicalTypesEncodingTest : StringSpec({
(36.hours + 24456.seconds).toJavaDuration()
)
)
// .generatesSchema(
// SchemaBuilder.record("LogicalTypes")
// .fields()
// .name("decimalBytes").type(SchemaBuilder.builder().bytesType().copy(logicalType = org.apache.avro.LogicalTypes.decimal(8, 2))).noDefault()
// .name("decimalFixed").type(SchemaBuilder.builder().fixed("decimalFixed").size(42).copy(logicalType = org.apache.avro.LogicalTypes.decimal(8, 2))).noDefault()
// .name("decimalString").type().stringType().noDefault()
// .name("date").type().intType().noDefault()
// .name("time").type().intType().noDefault()
// .name("instant").type().longType().noDefault()
// .name("instantMicros").type().longType().noDefault()
// .name("uuid").type().stringType().noDefault()
// .name("url").type().stringType().noDefault()
// .name("bigInteger").type().stringType().noDefault()
// .name("dateTime").type().longType().noDefault()
// .name("period").type(SchemaBuilder.fixed("duration").size(12).copy(logicalType = LogicalType("duration"))).noDefault()
// .name("javaDuration").type("duration").noDefault()
// .name("kotlinDuration").type("duration").noDefault()
// .endRecord()
// )
.isEncodedAs(
record(
Conversions.DecimalConversion().toBytes(
Expand Down Expand Up @@ -192,9 +174,9 @@ internal class LogicalTypesEncodingTest : StringSpec({
@Serializable
@SerialName("LogicalTypes")
private data class LogicalTypes(
@Contextual val decimalBytes: BigDecimal,
@Contextual @AvroFixed(42) val decimalFixed: BigDecimal,
@Serializable(BigDecimalAsStringSerializer::class) val decimalString: BigDecimal,
@Contextual @AvroDecimal(scale = 2, precision = 8) val decimalBytes: BigDecimal,
@Contextual @AvroDecimal(scale = 2, precision = 8) @AvroFixed(42) val decimalFixed: BigDecimal,
@Contextual @AvroStringable val decimalString: BigDecimal,
@Contextual val date: LocalDate,
@Contextual val time: LocalTime,
@Contextual val instant: Instant,
Expand All @@ -210,9 +192,9 @@ internal class LogicalTypesEncodingTest : StringSpec({

@Serializable
private data class NullableLogicalTypes(
@Contextual val decimalBytesNullable: BigDecimal?,
@Contextual @AvroFixed(42) val decimalFixedNullable: BigDecimal?,
@Serializable(BigDecimalAsStringSerializer::class) val decimalStringNullable: BigDecimal?,
@Contextual @AvroDecimal(scale = 2, precision = 8) val decimalBytesNullable: BigDecimal?,
@Contextual @AvroDecimal(scale = 2, precision = 8) @AvroFixed(42) val decimalFixedNullable: BigDecimal?,
@Contextual @AvroStringable val decimalStringNullable: BigDecimal?,
@Contextual val dateNullable: LocalDate?,
@Contextual val timeNullable: LocalTime?,
@Contextual val instantNullable: Instant?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ internal class MapEncodingTest : FunSpec({
}

test("support maps with contextual keys") {
AvroAssertions.assertThat<ContextualKeyTests>()
AvroAssertions.assertThat(ContextualKeyTests(mapOf(NonSerializableKey("a") to 12)))
.withConfig {
serializersModule = serializersModuleOf(NonSerializableKeyKSerializer)
}
Expand All @@ -131,10 +131,6 @@ internal class MapEncodingTest : FunSpec({
.name("map").type(Schema.createMap(Schema.create(Schema.Type.INT))).noDefault()
.endRecord()
)
AvroAssertions.assertThat(ContextualKeyTests(mapOf(NonSerializableKey("a") to 12)))
.withConfig {
serializersModule = serializersModuleOf(NonSerializableKeyKSerializer)
}
.isEncodedAs(record(mapOf("a" to 12)))
}

Expand All @@ -151,9 +147,8 @@ internal class MapEncodingTest : FunSpec({
SomeEnum.B
).forEach { keyValue ->
test("handle string-able key type: ${keyValue::class.simpleName}") {
AvroAssertions.assertThat(GenericMapForTests.serializer(keyValue::class.serializer()))
.generatesSchema(Path("/map_string.json"))
AvroAssertions.assertThat(GenericMapForTests(mapOf(keyValue to "something")), GenericMapForTests.serializer(keyValue::class.serializer()))
.generatesSchema(Path("/map_string.json"))
.isEncodedAs(record(mapOf(keyValue.toString() to "something")))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.github.avrokotlin.avro4k.schema

import com.github.avrokotlin.avro4k.Avro
import com.github.avrokotlin.avro4k.AvroAssertions
import com.github.avrokotlin.avro4k.AvroDecimal
import com.github.avrokotlin.avro4k.AvroDefault
import com.github.avrokotlin.avro4k.schema
import com.github.avrokotlin.avro4k.serializer.BigDecimalSerializer
Expand Down Expand Up @@ -117,14 +118,18 @@ internal class AvroDefaultSchemaTest : FunSpec() {

@Serializable
private data class BarDecimal(
@AvroDecimal(scale = 2, precision = 8)
@Serializable(BigDecimalSerializer::class)
val a: BigDecimal,
@AvroDecimal(scale = 2, precision = 8)
@Serializable(BigDecimalSerializer::class)
@AvroDefault("\u0000")
val b: BigDecimal,
@AvroDecimal(scale = 2, precision = 8)
@Serializable(BigDecimalSerializer::class)
@AvroDefault("null")
val nullableString: BigDecimal?,
@AvroDecimal(scale = 2, precision = 8)
@Serializable(BigDecimalSerializer::class)
@AvroDefault("\u0000")
val c: BigDecimal?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,26 @@ internal class BigDecimalSchemaTest : FunSpec({
@JvmInline
@Serializable
private value class BigDecimalTest(
@Contextual val bigDecimal: BigDecimal,
@AvroDecimal(scale = 2, precision = 8)
@Contextual
val bigDecimal: BigDecimal,
)

@JvmInline
@Serializable
@SerialName("BigDecimalFixedTest")
private value class BigDecimalFixedTest(
@AvroDecimal(3, 5) @AvroFixed(5) @Contextual val field: BigDecimal,
@AvroDecimal(scale = 3, precision = 5)
@AvroFixed(5)
@Contextual
val field: BigDecimal,
)

@JvmInline
@Serializable
private value class BigDecimalNullableTest(
@AvroDecimal(1, 2) @Contextual val bigDecimal: BigDecimal?,
@AvroDecimal(scale = 1, precision = 2)
@Contextual
val bigDecimal: BigDecimal?,
)
}

0 comments on commit eda4375

Please sign in to comment.