From dd71a97e274b7c55088a9b63fcf12f40283cdde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20=E2=80=9CCLOVIS=E2=80=9D=20Canet?= Date: Sat, 14 Feb 2026 16:38:16 +0100 Subject: [PATCH] feat(bson): Create UuidAsBsonBinarySerializer to handle the KxS configuration of the official driver --- .../src/commonMain/kotlin/raw/BinaryTest.kt | 7 +- bson/src/commonMain/kotlin/types/Uuid.kt | 106 ++++++++++++++++++ bson/src/jvmMain/kotlin/types/Uuid.jvm.kt | 50 +++++++++ .../nativeMain/kotlin/types/Uuid.native.kt | 30 +++++ .../kotlin/types/Uuid.wasmWasi.kt | 30 +++++ bson/src/webMain/kotlin/types/Uuid.web.kt | 30 +++++ 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 bson/src/commonMain/kotlin/types/Uuid.kt create mode 100644 bson/src/jvmMain/kotlin/types/Uuid.jvm.kt create mode 100644 bson/src/nativeMain/kotlin/types/Uuid.native.kt create mode 100644 bson/src/wasmWasiMain/kotlin/types/Uuid.wasmWasi.kt create mode 100644 bson/src/webMain/kotlin/types/Uuid.web.kt diff --git a/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt b/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt index 7bb122f1..266db3df 100644 --- a/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt +++ b/bson-tests/src/commonMain/kotlin/raw/BinaryTest.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, OpenSavvy and contributors. + * Copyright (c) 2025-2026, OpenSavvy and contributors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.hex import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.json import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.serialize import opensavvy.ktmongo.bson.raw.BsonDeclaration.Companion.verify +import opensavvy.ktmongo.bson.types.UuidAsBsonBinarySerializer import opensavvy.ktmongo.dsl.LowLevelApi import opensavvy.prepared.suite.Prepared import opensavvy.prepared.suite.SuiteDsl @@ -106,7 +107,9 @@ fun SuiteDsl.binary(context: Prepared) = suite("Binary") { ) @Serializable - data class U(val x: Uuid) + data class U( + val x: @Serializable(with = UuidAsBsonBinarySerializer::class) Uuid, + ) testBson( context, diff --git a/bson/src/commonMain/kotlin/types/Uuid.kt b/bson/src/commonMain/kotlin/types/Uuid.kt new file mode 100644 index 00000000..ff12c095 --- /dev/null +++ b/bson/src/commonMain/kotlin/types/Uuid.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2026, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.bson.types + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.UseSerializers +import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import opensavvy.ktmongo.bson.BsonType +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +/** + * Serializer for [kotlin.uuid.Uuid] that serializes as a [BsonType.BinaryData] with subtype 4 (UUID). + * + * The MongoDB official driver's KotlinX.Serialization support doesn't yet support [kotlin.uuid.Uuid] + * (see [JAVA-6083](https://jira.mongodb.org/browse/JAVA-6083)). This serializer adds this support. + * Once this ticket is fixed, this serializer will be deprecated. + * + * When KtMongo Multiplatform's BSON implementation is used, this serializer does nothing, as the default serializer + * already uses [BsonType.BinaryData]. + * + * ### Behavior with non-BSON formats + * + * When serializing to non-BSON formats (e.g., JSON, gRPC…), the UUID is encoded as a string in its standard + * hyphenated format (e.g., `550e8400-e29b-41d4-a716-446655440000`). + * + * ### Example + * + * The annotation [UseSerializers] can be used to configure all [Uuid] fields in that file to use this serializer: + * + * ```kotlin + * @file:UseSerializers(UuidAsBsonBinarySerializer::class) + * + * @Serializable + * data class Foo( + * val _id: ObjectId, + * val correlationId: Uuid, + * ) + * ``` + * + * Alternatively, [Serializable] can be used to configure a specific [Uuid] field to use this serializer: + * + * ```kotlin + * @Serializable + * data class Foo( + * val _id: ObjectId, + * val correlationId: @Serializable(with = UuidAsBsonBinarySerializer::class) Uuid, + * ) + * ``` + */ +@ExperimentalUuidApi +object UuidAsBsonBinarySerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = Uuid.serializer().descriptor + + override fun serialize(encoder: Encoder, value: Uuid) { + serializeUuidPlatformSpecific(encoder, value) + } + + override fun deserialize(decoder: Decoder): Uuid = + deserializeUuidPlatformSpecific(decoder) +} + +@ExperimentalUuidApi +internal fun serializeUuidAsString(encoder: Encoder, value: Uuid) { + encoder.encodeString(value.toString()) +} + +@ExperimentalUuidApi +internal fun deserializeUuidAsString(decoder: Decoder): Uuid = + Uuid.parse(decoder.decodeString()) + +/** + * On the JVM, when using KotlinX.Serialization with the official driver, we must hard-code a different behavior. + * + * All non-JVM platforms implement this function by calling [serializeUuidAsString]. + * This could be simplified with [KT-20427](https://youtrack.jetbrains.com/projects/KT/issues/KT-20427). + */ +@ExperimentalUuidApi +internal expect fun serializeUuidPlatformSpecific(encoder: Encoder, value: Uuid) + +/** + * On the JVM, when using KotlinX.Serialization with the official driver, we must hard-code a different behavior. + * + * All non-JVM platforms implement this function by calling [deserializeUuidAsString]. + * This could be simplified with [KT-20427](https://youtrack.jetbrains.com/projects/KT/issues/KT-20427). + */ +@ExperimentalUuidApi +internal expect fun deserializeUuidPlatformSpecific(decoder: Decoder): Uuid diff --git a/bson/src/jvmMain/kotlin/types/Uuid.jvm.kt b/bson/src/jvmMain/kotlin/types/Uuid.jvm.kt new file mode 100644 index 00000000..bc39e79a --- /dev/null +++ b/bson/src/jvmMain/kotlin/types/Uuid.jvm.kt @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.bson.types + +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import org.bson.BsonBinary +import org.bson.UuidRepresentation +import org.bson.codecs.kotlinx.BsonDecoder +import org.bson.codecs.kotlinx.BsonEncoder +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid +import kotlin.uuid.toJavaUuid +import kotlin.uuid.toKotlinUuid + +private val isOfficialKotlinSerializationEnabled = + ClassLoader.getSystemClassLoader().loadClass("org.bson.codecs.kotlinx.BsonEncoder") != null + +@OptIn(ExperimentalUuidApi::class, ExperimentalSerializationApi::class) +internal actual fun serializeUuidPlatformSpecific(encoder: Encoder, value: Uuid) { + if (isOfficialKotlinSerializationEnabled && encoder is BsonEncoder) { + encoder.encodeBsonValue(BsonBinary(value.toJavaUuid(), UuidRepresentation.STANDARD)) + } else { + serializeUuidAsString(encoder, value) + } +} + +@OptIn(ExperimentalSerializationApi::class) +@ExperimentalUuidApi +internal actual fun deserializeUuidPlatformSpecific(decoder: Decoder): Uuid = + if (isOfficialKotlinSerializationEnabled && decoder is BsonDecoder) { + val bsonBinary = decoder.decodeBsonValue() as BsonBinary + bsonBinary.asUuid(UuidRepresentation.STANDARD).toKotlinUuid() + } else { + deserializeUuidAsString(decoder) + } diff --git a/bson/src/nativeMain/kotlin/types/Uuid.native.kt b/bson/src/nativeMain/kotlin/types/Uuid.native.kt new file mode 100644 index 00000000..41d58e31 --- /dev/null +++ b/bson/src/nativeMain/kotlin/types/Uuid.native.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.bson.types + +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@ExperimentalUuidApi +internal actual fun serializeUuidPlatformSpecific(encoder: Encoder, value: Uuid) { + serializeUuidAsString(encoder, value) +} + +@ExperimentalUuidApi +internal actual fun deserializeUuidPlatformSpecific(decoder: Decoder): Uuid = + deserializeUuidAsString(decoder) diff --git a/bson/src/wasmWasiMain/kotlin/types/Uuid.wasmWasi.kt b/bson/src/wasmWasiMain/kotlin/types/Uuid.wasmWasi.kt new file mode 100644 index 00000000..41d58e31 --- /dev/null +++ b/bson/src/wasmWasiMain/kotlin/types/Uuid.wasmWasi.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.bson.types + +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@ExperimentalUuidApi +internal actual fun serializeUuidPlatformSpecific(encoder: Encoder, value: Uuid) { + serializeUuidAsString(encoder, value) +} + +@ExperimentalUuidApi +internal actual fun deserializeUuidPlatformSpecific(decoder: Decoder): Uuid = + deserializeUuidAsString(decoder) diff --git a/bson/src/webMain/kotlin/types/Uuid.web.kt b/bson/src/webMain/kotlin/types/Uuid.web.kt new file mode 100644 index 00000000..41d58e31 --- /dev/null +++ b/bson/src/webMain/kotlin/types/Uuid.web.kt @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, OpenSavvy and contributors. + * + * 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 opensavvy.ktmongo.bson.types + +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlin.uuid.ExperimentalUuidApi +import kotlin.uuid.Uuid + +@ExperimentalUuidApi +internal actual fun serializeUuidPlatformSpecific(encoder: Encoder, value: Uuid) { + serializeUuidAsString(encoder, value) +} + +@ExperimentalUuidApi +internal actual fun deserializeUuidPlatformSpecific(decoder: Decoder): Uuid = + deserializeUuidAsString(decoder) -- 2.51.2