Skip to content

Commit

Permalink
feat: Separate naming strategies (avro-kotlin#178), remove AvroName[s…
Browse files Browse the repository at this point in the history
…pace] and add AvroNamespaceOverride (avro-kotlin#165)
  • Loading branch information
Chuckame committed Feb 3, 2024
1 parent 24c9f2b commit f631be7
Show file tree
Hide file tree
Showing 29 changed files with 328 additions and 325 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,12 +19,6 @@ class AnnotationExtractor(private val annotations: List<Annotation>) {

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

fun scalePrecision(): Pair<Int, Int>? = annotations.filterIsInstance<ScalePrecision>().firstOrNull()?.let { it.scale to it.precision }

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

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

fun valueType(): Boolean = annotations.filterIsInstance<AvroInline>().isNotEmpty()

fun doc(): String? = annotations.filterIsInstance<AvroDoc>().firstOrNull()?.value
Expand All @@ -34,7 +28,7 @@ class AnnotationExtractor(private val annotations: List<Annotation>) {
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, AnnotationTarget.CLASS)
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 @@ -97,18 +90,14 @@ annotation class AvroInline
@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>)

/**
* [AvroFixed] overrides the schema type for a field or a value class
* so that the schema is set to org.apache.avro.Schema.Type.FIXED
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 @@ -91,7 +90,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 @@ -100,7 +99,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,6 +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 +20,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).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 +42,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 f631be7

Please sign in to comment.