Skip to content

Commit

Permalink
fixes #291 kotlinx Serialization does nos support binary standard uui…
Browse files Browse the repository at this point in the history
…dRepresentation
  • Loading branch information
zigzago committed Sep 11, 2021
1 parent 1878ea8 commit e7af1d6
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,22 @@ package org.litote.kmongo.issues
import com.mongodb.MongoClientSettings
import com.mongodb.client.MongoClient
import com.mongodb.client.MongoCollection
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable
import org.bson.UuidRepresentation
import org.junit.Test
import org.junit.experimental.categories.Category
import org.litote.kmongo.KFlapdoodle
import org.litote.kmongo.KMongo
import org.litote.kmongo.KMongoRootTest
import org.litote.kmongo.NativeMappingCategory
import org.litote.kmongo.findOne
import org.litote.kmongo.getCollection
import org.litote.kmongo.save
import java.util.UUID
import kotlin.test.assertEquals

data class UUIDContainer(val _id: UUID)
@Serializable
data class UUIDContainer(val _id: @Contextual UUID)


@Category(NativeMappingCategory::class)
class Issue245UuidRepresentation : KMongoRootTest() {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ internal object ReplicaSetEmbeddedMongo {

return mongodProcesses.run {
ConnectionString(
"mongodb://${first().host},${get(1).host},${get(2).host}/?replicaSet=kmongo"
"mongodb://${first().host},${get(1).host},${get(2).host}/?replicaSet=kmongo&uuidRepresentation=standard"
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import kotlinx.serialization.builtins.PairSerializer
import kotlinx.serialization.builtins.TripleSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.overwriteWith
import kotlinx.serialization.serializer
import kotlinx.serialization.serializerOrNull
import org.bson.BsonTimestamp
Expand All @@ -52,6 +53,7 @@ import java.util.Calendar
import java.util.Date
import java.util.GregorianCalendar
import java.util.Locale
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
import java.util.regex.Pattern
Expand Down Expand Up @@ -120,7 +122,8 @@ internal object KMongoSerializationRepository {
StringId::class to IdSerializer(true),
WrappedObjectId::class to IdSerializer(false),
Pattern::class to PatternSerializer,
Regex::class to RegexSerializer
Regex::class to RegexSerializer,
UUID::class to UUIDSerializer
)

@ExperimentalSerializationApi
Expand All @@ -145,10 +148,10 @@ internal object KMongoSerializationRepository {
} as KSerializer<Any>
)
else -> module.getContextual(kClass)
?: findPolymorphic(kClass, obj)?.let {
PolymorphicSerializer(it)
}
?: findSealed(kClass)?.serializerOrNull()
?: findPolymorphic(kClass, obj)?.let {
PolymorphicSerializer(it)
}
?: findSealed(kClass)?.serializerOrNull()
}
}

Expand All @@ -158,14 +161,14 @@ internal object KMongoSerializationRepository {
private fun <T : Any> findPolymorphic(kClass: KClass<*>, obj: T): KClass<*>? =
module.getPolymorphic(kClass as KClass<T>, obj)
?.let { kClass }
?: kClass.superclasses.asSequence().map { findPolymorphic(it, obj) }.filterNotNull().firstOrNull()
?: kClass.superclasses.asSequence().map { findPolymorphic(it, obj) }.filterNotNull().firstOrNull()

@ExperimentalSerializationApi
@InternalSerializationApi
@Suppress("UNCHECKED_CAST")
private fun findSealed(kClass: KClass<*>): KClass<*>? =
kClass.takeIf { it.isSealed }
?: kClass.superclasses.asSequence().map { findSealed(it) }.filterNotNull().firstOrNull()
?: kClass.superclasses.asSequence().map { findSealed(it) }.filterNotNull().firstOrNull()


@ExperimentalSerializationApi
Expand All @@ -176,9 +179,9 @@ internal object KMongoSerializationRepository {
error("no serializer for null")
} else {
(serializersMap[kClass]
?: getBaseSerializer(obj, kClass)
?: kClass.serializer()) as? KSerializer<T>
?: error("no serializer for $obj of class $kClass")
?: getBaseSerializer(obj, kClass)
?: kClass.serializer()) as? KSerializer<T>
?: error("no serializer for $obj of class $kClass")
}

@ExperimentalSerializationApi
Expand All @@ -189,38 +192,43 @@ internal object KMongoSerializationRepository {
error("no serializer for null")
} else {
(serializersMap[obj.javaClass.kotlin]
?: getBaseSerializer(obj)
?: obj.javaClass.kotlin.serializer()) as? KSerializer<T>
?: error("no serializer for $obj of class ${obj.javaClass.kotlin}")
?: getBaseSerializer(obj)
?: obj.javaClass.kotlin.serializer()) as? KSerializer<T>
?: error("no serializer for $obj of class ${obj.javaClass.kotlin}")
}

@ExperimentalSerializationApi
@InternalSerializationApi
@Suppress("UNCHECKED_CAST")
fun <T : Any> getSerializer(kClass: KClass<T>): KSerializer<T> =
(serializersMap[kClass]
?: module.getContextual(kClass)
?: try {
kClass.serializer()
} catch (e: SerializationException) {
if (kClass.isAbstract || kClass.isOpen || kClass.isSealed) {
PolymorphicSerializer(kClass)
} else {
throw e
}
?: module.getContextual(kClass)
?: try {
kClass.serializer()
} catch (e: SerializationException) {
if (kClass.isAbstract || kClass.isOpen || kClass.isSealed) {
PolymorphicSerializer(kClass)
} else {
throw e
}
}
) as? KSerializer<T>
?: error("no serializer for $kClass of class $kClass")
?: error("no serializer for $kClass of class $kClass")

@Volatile
private var baseModule: SerializersModule = initBaseModule()

@Suppress("UNCHECKED_CAST")
private fun initBaseModule(): SerializersModule = SerializersModule {
serializersMap.forEach { contextual(it.key as KClass<Any>, it.value as KSerializer<Any>) }
customSerializersMap.forEach { contextual(it.key as KClass<Any>, it.value as KSerializer<Any>) }
customModules.forEach { include(it) }
}
private fun initBaseModule(): SerializersModule =
SerializersModule {
serializersMap.forEach { contextual(it.key as KClass<Any>, it.value as KSerializer<Any>) }
}
.overwriteWith(
SerializersModule {
customSerializersMap.forEach { contextual(it.key as KClass<Any>, it.value as KSerializer<Any>) }
customModules.forEach { include(it) }
}
)

val module: SerializersModule
get() {
Expand Down
13 changes: 13 additions & 0 deletions kmongo-serialization-mapping/src/main/kotlin/Serializers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.litote.kmongo.serialization

import com.github.jershell.kbson.BsonEncoder
import com.github.jershell.kbson.BsonFlexibleDecoder
import com.github.jershell.kbson.FlexibleDecoder
import com.github.jershell.kbson.ObjectIdSerializer
import kotlinx.serialization.KSerializer
Expand Down Expand Up @@ -51,6 +52,7 @@ import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.UUID
import java.util.regex.Pattern
import kotlin.reflect.KProperty

Expand Down Expand Up @@ -285,4 +287,15 @@ internal object RegexSerializer : KSerializer<Regex> {
e.encodeStringElement(descriptor, 1, PatternUtil.getOptionsAsString(value.toPattern()))
e.endStructure(descriptor)
}
}

internal object UUIDSerializer : KSerializer<UUID> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("UuidSerializer")

override fun deserialize(decoder: Decoder): UUID =
(decoder as BsonFlexibleDecoder).reader.readBinaryData().asUuid()

override fun serialize(encoder: Encoder, value: UUID) {
(encoder as BsonEncoder).encodeUUID(value)
}
}
99 changes: 99 additions & 0 deletions kmongo-serialization/src/test/kotlin/Issue287UUID.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2016/2021 Litote
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.litote.kmongo.issues

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.Serializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import org.bson.BsonReader
import org.bson.BsonWriter
import org.bson.Document
import org.bson.codecs.Codec
import org.bson.codecs.DecoderContext
import org.bson.codecs.EncoderContext
import org.bson.codecs.configuration.CodecRegistries
import org.junit.Test
import org.litote.kmongo.AllCategoriesKMongoBaseTest
import org.litote.kmongo.eq
import org.litote.kmongo.findOne
import org.litote.kmongo.serialization.registerSerializer
import org.litote.kmongo.withDocumentClass
import java.util.UUID
import kotlin.test.assertNotNull

/**
*
*/
@ExperimentalSerializationApi
@Serializer(forClass = UUID::class)
object EntitySerializer : KSerializer<UUID> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor(
serialName = "com.malachid.poc.Entity",
kind = PrimitiveKind.STRING
)

override fun deserialize(decoder: Decoder): UUID =
UUID.fromString(decoder.decodeString())

override fun serialize(encoder: Encoder, value: UUID) {
encoder.encodeString(value.toString())
}
}

@ExperimentalSerializationApi
@Serializable
data class ClassUUID(val _id: @Serializable(with = EntitySerializer::class) UUID)

/**
*
*/
@ExperimentalSerializationApi
class Issue287UUID : AllCategoriesKMongoBaseTest<ClassUUID>() {

@Test
fun canFindOne() {
val c = col.withCodecRegistry(
CodecRegistries.fromRegistries(
CodecRegistries.fromCodecs(
object : Codec<UUID> {
override fun encode(writer: BsonWriter, value: UUID, encoderContext: EncoderContext) {
writer.writeString(value.toString())
}

override fun getEncoderClass(): Class<UUID> = UUID::class.java

override fun decode(reader: BsonReader, decoderContext: DecoderContext): UUID {
return UUID.fromString(reader.readString())
}
}
),
col.codecRegistry
))
registerSerializer(EntitySerializer)
val uuid = UUID.randomUUID()
c.insertOne(ClassUUID(uuid))
assertNotNull(c.findOne(ClassUUID::_id eq uuid))
}

}

0 comments on commit e7af1d6

Please sign in to comment.