Skip to content

Commit

Permalink
feat: Separate naming strategies (#178), remove AvroName[space] and a…
Browse files Browse the repository at this point in the history
…dd AvroNamespaceOverride (#165)
  • Loading branch information
Chuckame committed Apr 10, 2024
1 parent 3cf493a commit f85e658
Show file tree
Hide file tree
Showing 29 changed files with 329 additions and 324 deletions.
10 changes: 5 additions & 5 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,24 @@ rootProject.name = "avro4k-core"
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
version("kotlin", "1.8.20")
version("kotlin", "1.9.22")
version("jvm", "18")

library("xerial-snappy", "org.xerial.snappy", "snappy-java").version("1.1.10.1")
library("apache-avro", "org.apache.avro", "avro").version("1.11.3")

val kotlinxSerialization = "1.5.0"
val kotlinxSerialization = "1.6.2"
library("kotlinx-serialization-core", "org.jetbrains.kotlinx", "kotlinx-serialization-core").version(kotlinxSerialization)
library("kotlinx-serialization-json", "org.jetbrains.kotlinx", "kotlinx-serialization-json").version(kotlinxSerialization)

val kotestVersion = "5.6.1"
val kotestVersion = "5.8.0"
library("kotest-core", "io.kotest", "kotest-assertions-core").version(kotestVersion)
library("kotest-json", "io.kotest", "kotest-assertions-json").version(kotestVersion)
library("kotest-junit5", "io.kotest", "kotest-runner-junit5").version(kotestVersion)
library("kotest-property", "io.kotest", "kotest-property").version(kotestVersion)

plugin("dokka", "org.jetbrains.dokka").version("1.8.10")
plugin("kotest", "io.kotest").version("0.4.10")
plugin("dokka", "org.jetbrains.dokka").version("1.9.10")
plugin("kotest", "io.kotest").version("0.4.11")
plugin("github-versions", "com.github.ben-manes.versions").version("0.46.0")
plugin("nexus-publish", "io.github.gradle-nexus.publish-plugin").version("1.3.0")
plugin("spotless", "com.diffplug.spotless").version("6.25.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,14 @@ class AnnotationExtractor(private val annotations: List<Annotation>) {

fun fixed(): Int? = annotations.filterIsInstance<AvroFixed>().firstOrNull()?.size

fun namespace(): String? = annotations.filterIsInstance<AvroNamespace>().firstOrNull()?.value

fun name(): String? = annotations.filterIsInstance<AvroName>().firstOrNull()?.value

fun doc(): String? = annotations.filterIsInstance<AvroDoc>().firstOrNull()?.value

fun aliases(): List<String> =
(
annotations.firstNotNullOfOrNull {
it as? AvroAlias
}?.value ?: emptyArray()
).asList() + (annotations.firstNotNullOfOrNull { it as? AvroAliases }?.value ?: emptyArray())
).asList()

fun props(): List<Pair<String, String>> = annotations.filterIsInstance<AvroProp>().map { it.key to it.value }

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/com/github/avrokotlin/avro4k/Avro.kt
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ class Avro(
obj: T,
): GenericRecord {
var record: Record? = null
val encoder = RootRecordEncoder(schema, serializersModule) { record = it }
val encoder = RootRecordEncoder(schema, serializersModule, configuration) { record = it }
encoder.encodeSerializableValue(serializer, obj)
return record!!
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.github.avrokotlin.avro4k

import com.github.avrokotlin.avro4k.schema.DefaultNamingStrategy
import com.github.avrokotlin.avro4k.schema.NamingStrategy
import com.github.avrokotlin.avro4k.schema.FieldNamingStrategy
import com.github.avrokotlin.avro4k.schema.RecordNamingStrategy

data class AvroConfiguration(
val namingStrategy: NamingStrategy = DefaultNamingStrategy,
val recordNamingStrategy: RecordNamingStrategy = RecordNamingStrategy.Default,
val fieldNamingStrategy: FieldNamingStrategy = FieldNamingStrategy.Default,
/**
* By default, during decoding, any missing value for a nullable field without default [null] value (e.g. `val field: Type?` without `= null`) is failing.
* When set to [true], the nullable fields that haven't any default value are set as null if the value is missing. It also adds `"default": null` to those fields when generating schema using avro4k.
Expand Down
32 changes: 0 additions & 32 deletions src/main/kotlin/com/github/avrokotlin/avro4k/FieldNaming.kt

This file was deleted.

77 changes: 0 additions & 77 deletions src/main/kotlin/com/github/avrokotlin/avro4k/RecordNaming.kt

This file was deleted.

37 changes: 13 additions & 24 deletions src/main/kotlin/com/github/avrokotlin/avro4k/annotations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@ import org.apache.avro.Schema
import org.apache.avro.SchemaBuilder
import org.intellij.lang.annotations.Language

/**
* When annotated on a property, overrides the namespace for the nested record.
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
annotation class AvroNamespaceOverride(val value: String)

@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroProp(val key: String, val value: String)
Expand All @@ -21,14 +28,6 @@ annotation class AvroJsonProp(
@Language("JSON") val jsonValue: String,
)

@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroNamespace(val value: String)

@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroName(val value: String)

@SerialInfo
@Target(AnnotationTarget.PROPERTY)
annotation class ScalePrecision(val scale: Int = 2, val precision: Int = 8)
Expand All @@ -55,35 +54,29 @@ annotation class AvroUuidLogicalType
@Target(AnnotationTarget.PROPERTY)
annotation class AvroTimeLogicalType(val type: LogicalTimeTypeEnum)

enum class LogicalTimeTypeEnum(val logicalTypeName: String, val kind: PrimitiveKind, val schemaFor: () -> Schema) {
DATE("date", PrimitiveKind.INT, { LogicalTypes.date().addToSchema(SchemaBuilder.builder().intType()) }),
enum class LogicalTimeTypeEnum(val kind: PrimitiveKind, val schemaFor: () -> Schema) {
DATE(PrimitiveKind.INT, { LogicalTypes.date().addToSchema(SchemaBuilder.builder().intType()) }),
TIME_MILLIS(
"time-millis",
PrimitiveKind.INT,
{ LogicalTypes.timeMillis().addToSchema(SchemaBuilder.builder().intType()) }
),
TIME_MICROS(
"time-micros",
PrimitiveKind.LONG,
{ LogicalTypes.timeMicros().addToSchema(SchemaBuilder.builder().longType()) }
),
TIMESTAMP_MILLIS(
"timestamp-millis",
PrimitiveKind.LONG,
{ LogicalTypes.timestampMillis().addToSchema(SchemaBuilder.builder().longType()) }
),
TIMESTAMP_MICROS(
"timestamp-micros",
PrimitiveKind.LONG,
{ LogicalTypes.timestampMicros().addToSchema(SchemaBuilder.builder().longType()) }
),
LOCAL_TIMESTAMP_MILLIS(
"local-timestamp-millis",
PrimitiveKind.LONG,
{ LogicalTypes.localTimestampMillis().addToSchema(SchemaBuilder.builder().longType()) }
),
LOCAL_TIMESTAMP_MICROS(
"local-timestamp-micros",
PrimitiveKind.LONG,
{ LogicalTypes.localTimestampMicros().addToSchema(SchemaBuilder.builder().longType()) }
),
Expand All @@ -93,18 +86,14 @@ enum class LogicalTimeTypeEnum(val logicalTypeName: String, val kind: PrimitiveK
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroDoc(val value: String)

/**
* Adds aliases to a field of a record. It helps to allow having different names for the same field for better compatibility when changing a schema.
* @param value The aliases for the annotated property. Note that the given aliases won't be changed by the configured [AvroConfiguration.fieldNamingStrategy].
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroAlias(vararg val value: String)

@SerialInfo
@Deprecated(
message = "Will be removed in the next major release",
replaceWith = ReplaceWith("@AvroAlias(alias1, alias2)")
)
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class AvroAliases(val value: Array<String>)

/**
* Indicates that the annotated property should be encoded as an Avro fixed type.
* @param size The number of bytes of the fixed type. Note that smaller values will be padded with 0s during encoding, but not unpadded when decoding.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.github.avrokotlin.avro4k.decoder

import com.github.avrokotlin.avro4k.AnnotationExtractor
import com.github.avrokotlin.avro4k.AvroConfiguration
import com.github.avrokotlin.avro4k.FieldNaming
import com.github.avrokotlin.avro4k.schema.extractNonNull
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
Expand Down Expand Up @@ -88,7 +87,7 @@ class RecordDecoder(
return record.get(resolvedFieldName())
}

FieldNaming(desc, currentIndex).aliases().forEach {
AnnotationExtractor(desc.getElementAnnotations(currentIndex)).aliases().forEach {
if (record.hasField(it)) {
return record.get(it)
}
Expand All @@ -97,7 +96,7 @@ class RecordDecoder(
return null
}

private fun resolvedFieldName(): String = configuration.namingStrategy.to(FieldNaming(desc, currentIndex).name())
private fun resolvedFieldName(): String = configuration.fieldNamingStrategy.resolve(desc, currentIndex, desc.getElementName(currentIndex))

private fun field(): Schema.Field = record.schema.getField(resolvedFieldName())

Expand Down
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.AvroConfiguration
import com.github.avrokotlin.avro4k.RecordNaming
import com.github.avrokotlin.avro4k.possibleSerializationSubclasses
import com.github.avrokotlin.avro4k.schema.RecordName
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
Expand Down Expand Up @@ -33,8 +33,8 @@ class UnionDecoder(

private var leafDescriptor: SerialDescriptor =
descriptor.possibleSerializationSubclasses(serializersModule).firstOrNull {
val schemaName = RecordNaming(value.schema.fullName, emptyList())
val serialName = RecordNaming(it)
val schemaName = RecordName(name = value.schema.name, namespace = value.schema.namespace)
val serialName = configuration.recordNamingStrategy.resolve(it, it.serialName)
serialName == schemaName
} ?: throw SerializationException("Cannot find a subtype of ${descriptor.serialName} that can be used to deserialize a record of schema ${value.schema}.")

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.avrokotlin.avro4k.encoder

import com.github.avrokotlin.avro4k.AvroConfiguration
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.AbstractEncoder
Expand All @@ -14,6 +15,7 @@ import java.nio.ByteBuffer
class ListEncoder(
private val schema: Schema,
override val serializersModule: SerializersModule,
override val configuration: AvroConfiguration,
private val callback: (GenericData.Array<Any?>) -> Unit,
) : AbstractEncoder(), StructureEncoder {
private val list = mutableListOf<Any?>()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.github.avrokotlin.avro4k.encoder

import com.github.avrokotlin.avro4k.AvroConfiguration
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerializationException
import kotlinx.serialization.descriptors.SerialDescriptor
Expand All @@ -15,6 +16,7 @@ import java.nio.ByteBuffer
class MapEncoder(
schema: Schema,
override val serializersModule: SerializersModule,
override val configuration: AvroConfiguration,
private val callback: (Map<Utf8, *>) -> Unit,
) : AbstractEncoder(),
CompositeEncoder,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.github.avrokotlin.avro4k.encoder

import com.github.avrokotlin.avro4k.AnnotationExtractor
import com.github.avrokotlin.avro4k.AvroConfiguration
import com.github.avrokotlin.avro4k.ListRecord
import com.github.avrokotlin.avro4k.Record
import com.github.avrokotlin.avro4k.schema.extractNonNull
Expand All @@ -19,17 +21,19 @@ import java.nio.ByteBuffer

@ExperimentalSerializationApi
interface StructureEncoder : FieldEncoder {
val configuration: AvroConfiguration

override fun beginStructure(descriptor: SerialDescriptor): CompositeEncoder {
return when (descriptor.kind) {
StructureKind.LIST -> {
when (descriptor.getElementDescriptor(0).unwrapValueClass.kind) {
PrimitiveKind.BYTE -> ByteArrayEncoder(fieldSchema(), serializersModule) { addValue(it) }
else -> ListEncoder(fieldSchema(), serializersModule) { addValue(it) }
else -> ListEncoder(fieldSchema(), serializersModule, configuration) { addValue(it) }
}
}
StructureKind.CLASS -> RecordEncoder(fieldSchema(), serializersModule) { addValue(it) }
StructureKind.MAP -> MapEncoder(fieldSchema(), serializersModule) { addValue(it) }
is PolymorphicKind -> UnionEncoder(fieldSchema(), serializersModule) { addValue(it) }
StructureKind.CLASS -> RecordEncoder(fieldSchema(), serializersModule, configuration) { addValue(it) }
StructureKind.MAP -> MapEncoder(fieldSchema(), serializersModule, configuration) { addValue(it) }
is PolymorphicKind -> UnionEncoder(fieldSchema(), serializersModule, configuration) { addValue(it) }
else -> throw SerializationException(".beginStructure was called on a non-structure type [$descriptor]")
}
}
Expand All @@ -39,6 +43,7 @@ interface StructureEncoder : FieldEncoder {
class RecordEncoder(
private val schema: Schema,
override val serializersModule: SerializersModule,
override val configuration: AvroConfiguration,
val callback: (Record) -> Unit,
) : AbstractEncoder(), StructureEncoder {
private val builder = RecordBuilder(schema)
Expand Down
Loading

0 comments on commit f85e658

Please sign in to comment.