diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt index 392de68852..6409c164a6 100644 --- a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/types/update/abstracts/Update.kt @@ -7,6 +7,7 @@ import dev.inmo.tgbotapi.types.updateIdField import dev.inmo.tgbotapi.utils.RiskFeature import dev.inmo.tgbotapi.utils.decodeDataAndJson import dev.inmo.tgbotapi.utils.nonstrictJsonFormat +import dev.inmo.tgbotapi.utils.serializers.CallbackCustomizableDeserializationStrategy import kotlinx.serialization.* import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder @@ -47,52 +48,21 @@ object UpdateSerializerWithoutSerialization : KSerializer { * @see StringFormat.parse * @see kotlinx.serialization.json.Json.parse */ -object UpdateDeserializationStrategy : DeserializationStrategy { - private val _customDeserializationStrategies = LinkedHashSet() - - /** - * Contains [JsonDeserializerStrategy] which will be used in [deserialize] method when standard - * [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well) - */ - val customDeserializationStrategies: Set - get() = _customDeserializationStrategies.toSet() - fun interface JsonDeserializerStrategy { - fun deserializeOrNull(json: JsonElement): Update? +object UpdateDeserializationStrategy : CallbackCustomizableDeserializationStrategy( + JsonElement.serializer().descriptor, + { _, jsonElement -> + nonstrictJsonFormat.decodeFromJsonElement( + RawUpdate.serializer(), + jsonElement!! + ).asUpdate( + jsonElement + ) + }, + { it, _, jsonElement -> + UnknownUpdate( + UpdateId((jsonElement as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L), + jsonElement!!, + it + ) } - - override val descriptor: SerialDescriptor = JsonElement.serializer().descriptor - - override fun deserialize(decoder: Decoder): Update { - val asJson = JsonElement.serializer().deserialize(decoder) - return runCatching { - nonstrictJsonFormat.decodeFromJsonElement( - RawUpdate.serializer(), - asJson - ).asUpdate( - asJson - ) - }.getOrElse { - customDeserializationStrategies.firstNotNullOfOrNull { - it.deserializeOrNull(asJson) - } ?: UnknownUpdate( - UpdateId((asJson as? JsonObject) ?.get(updateIdField) ?.jsonPrimitive ?.longOrNull ?: -1L), - asJson, - it - ) - } - } - - /** - * Adding [deserializationStrategy] into [customDeserializationStrategies] for using in case of unknown update - */ - fun addUpdateDeserializationStrategy( - deserializationStrategy: JsonDeserializerStrategy - ) = _customDeserializationStrategies.add(deserializationStrategy) - - /** - * Removing [deserializationStrategy] from [customDeserializationStrategies] - */ - fun removeUpdateDeserializationStrategy( - deserializationStrategy: JsonDeserializerStrategy - ) = _customDeserializationStrategies.remove(deserializationStrategy) -} +) diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableDeserializationStrategy.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableDeserializationStrategy.kt new file mode 100644 index 0000000000..c29c232381 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableDeserializationStrategy.kt @@ -0,0 +1,78 @@ +package dev.inmo.tgbotapi.utils.serializers + +import dev.inmo.tgbotapi.types.update.RawUpdate +import dev.inmo.tgbotapi.types.update.abstracts.Update +import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy.deserialize +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement + +interface CustomizableDeserializationStrategy : DeserializationStrategy { + fun interface JsonDeserializerStrategy { + fun deserializeOrNull(json: JsonElement): T? + } + /** + * Contains [JsonDeserializerStrategy] which will be used in [deserialize] method when standard + * [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well) + */ + val customDeserializationStrategies: Set> + + /** + * Adding [deserializationStrategy] into [customDeserializationStrategies] for using in case of unknown update + */ + fun addUpdateDeserializationStrategy( + deserializationStrategy: JsonDeserializerStrategy + ): Boolean + + /** + * Removing [deserializationStrategy] from [customDeserializationStrategies] + */ + fun removeUpdateDeserializationStrategy( + deserializationStrategy: JsonDeserializerStrategy + ): Boolean +} + +open class CallbackCustomizableDeserializationStrategy( + override val descriptor: SerialDescriptor, + private val defaultDeserializeCallback: (decoder: Decoder, jsonElement: JsonElement?) -> T, + private val fallbackDeserialization: (initialException: Throwable, decoder: Decoder, jsonElement: JsonElement?) -> T = { initialException, _, _ -> throw initialException } +) : CustomizableDeserializationStrategy { + protected val _customDeserializationStrategies = LinkedHashSet>() + + /** + * Contains [JsonDeserializerStrategy] which will be used in [deserialize] method when standard + * [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well) + */ + override val customDeserializationStrategies: Set> + get() = _customDeserializationStrategies.toSet() + + override fun deserialize(decoder: Decoder): T { + val jsonDecoder = decoder as? JsonDecoder + val jsonElement = jsonDecoder ?.decodeJsonElement() + return runCatching { + defaultDeserializeCallback(decoder, jsonElement) + }.onFailure { + return (jsonElement ?.let { + customDeserializationStrategies.firstNotNullOfOrNull { + it.deserializeOrNull(jsonElement) + } + }) ?: fallbackDeserialization(it, decoder, jsonElement) + }.getOrThrow() + } + + /** + * Adding [deserializationStrategy] into [customDeserializationStrategies] for using in case of unknown update + */ + override fun addUpdateDeserializationStrategy( + deserializationStrategy: CustomizableDeserializationStrategy.JsonDeserializerStrategy + ): Boolean = _customDeserializationStrategies.add(deserializationStrategy) + + /** + * Removing [deserializationStrategy] from [customDeserializationStrategies] + */ + override fun removeUpdateDeserializationStrategy( + deserializationStrategy: CustomizableDeserializationStrategy.JsonDeserializerStrategy + ): Boolean = _customDeserializationStrategies.remove(deserializationStrategy) +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableSerializationStrategy.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableSerializationStrategy.kt new file mode 100644 index 0000000000..9a81bd64e4 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableSerializationStrategy.kt @@ -0,0 +1,74 @@ +package dev.inmo.tgbotapi.utils.serializers + +import dev.inmo.tgbotapi.types.update.RawUpdate +import dev.inmo.tgbotapi.types.update.abstracts.Update +import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy.deserialize +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonElement + +interface CustomizableSerializationStrategy : SerializationStrategy { + fun interface CustomSerializerStrategy { + fun optionallySerialize(encoder: Encoder, value: T): Boolean + } + /** + * Contains [CustomSerializerStrategy] which will be used in [Serialize] method when standard + * [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well) + */ + val customSerializationStrategies: Set> + + /** + * Adding [deserializationStrategy] into [customSerializationStrategies] for using in case of unknown update + */ + fun addUpdateSerializationStrategy( + deserializationStrategy: CustomSerializerStrategy + ): Boolean + + /** + * Removing [deserializationStrategy] from [customSerializationStrategies] + */ + fun removeUpdateSerializationStrategy( + deserializationStrategy: CustomSerializerStrategy + ): Boolean +} + +open class CallbackCustomizableSerializationStrategy( + override val descriptor: SerialDescriptor, + private val defaultSerializeCallback: (encoder: Encoder, value: T) -> Unit, + private val fallbackSerialization: (initialException: Throwable, encoder: Encoder, value: T) -> T = { initialException, _, _ -> throw initialException } +) : CustomizableSerializationStrategy { + protected val _customSerializationStrategies = LinkedHashSet>() + + /** + * Contains [JsonSerializerStrategy] which will be used in [deserialize] method when standard + * [RawUpdate] serializer will be unable to create [RawUpdate] (and [Update] as well) + */ + override val customSerializationStrategies: Set> + get() = _customSerializationStrategies.toSet() + + override fun serialize(encoder: Encoder, value: T) { + runCatching { + defaultSerializeCallback(encoder, value) + }.onFailure { + customSerializationStrategies.firstOrNull() { + it.optionallySerialize(encoder, value) + } ?: fallbackSerialization(it, encoder, value) + } + } + + /** + * Adding [deserializationStrategy] into [customSerializationStrategies] for using in case of unknown update + */ + override fun addUpdateSerializationStrategy( + deserializationStrategy: CustomizableSerializationStrategy.CustomSerializerStrategy + ): Boolean = _customSerializationStrategies.add(deserializationStrategy) + + /** + * Removing [deserializationStrategy] from [customSerializationStrategies] + */ + override fun removeUpdateSerializationStrategy( + deserializationStrategy: CustomizableSerializationStrategy.CustomSerializerStrategy + ): Boolean = _customSerializationStrategies.remove(deserializationStrategy) +} diff --git a/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableSerializer.kt b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableSerializer.kt new file mode 100644 index 0000000000..4a386b25a3 --- /dev/null +++ b/tgbotapi.core/src/commonMain/kotlin/dev/inmo/tgbotapi/utils/serializers/CustomizableSerializer.kt @@ -0,0 +1,27 @@ +package dev.inmo.tgbotapi.utils.serializers + +import dev.inmo.tgbotapi.types.update.RawUpdate +import dev.inmo.tgbotapi.types.update.abstracts.Update +import dev.inmo.tgbotapi.types.update.abstracts.UpdateDeserializationStrategy.deserialize +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.KSerializer +import kotlinx.serialization.Serializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.JsonElement + +interface CustomizableSerializer : KSerializer, CustomizableSerializationStrategy, CustomizableDeserializationStrategy { +} + +open class CallbacksCustomizableDeserializationStrategy( + override val descriptor: SerialDescriptor, + defaultDeserializeCallback: (decoder: Decoder, jsonElement: JsonElement?) -> T, + defaultSerializeCallback: (encoder: Encoder, value: T) -> Unit, + fallbackDeserialization: (initialException: Throwable, decoder: Decoder, jsonElement: JsonElement?) -> T = { initialException, _, _ -> throw initialException }, + fallbackSerialization: (initialException: Throwable, encoder: Encoder, value: T) -> T = { initialException, _, _ -> throw initialException } +) : CustomizableSerializer, + CustomizableSerializationStrategy by CallbackCustomizableSerializationStrategy(descriptor, defaultSerializeCallback, fallbackSerialization), + CustomizableDeserializationStrategy by CallbackCustomizableDeserializationStrategy(descriptor, defaultDeserializeCallback, fallbackDeserialization){ +}