Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite VoiceEvent.DeserializationStrategy #925

Merged
merged 3 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions voice/api/voice.api
Original file line number Diff line number Diff line change
Expand Up @@ -577,17 +577,6 @@ public final class dev/kord/voice/gateway/HeartbeatAck : dev/kord/voice/gateway/
public fun toString ()Ljava/lang/String;
}

public final class dev/kord/voice/gateway/HeartbeatAck$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Ldev/kord/voice/gateway/HeartbeatAck$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/kord/voice/gateway/HeartbeatAck;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/kord/voice/gateway/HeartbeatAck;)V
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}

public final class dev/kord/voice/gateway/HeartbeatAck$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
Expand Down Expand Up @@ -656,6 +645,7 @@ public final class dev/kord/voice/gateway/Identify$Companion {

public final class dev/kord/voice/gateway/OpCode : java/lang/Enum {
public static final field ClientDisconnect Ldev/kord/voice/gateway/OpCode;
public static final field Companion Ldev/kord/voice/gateway/OpCode$Companion;
public static final field Heartbeat Ldev/kord/voice/gateway/OpCode;
public static final field HeartbeatAck Ldev/kord/voice/gateway/OpCode;
public static final field Hello Ldev/kord/voice/gateway/OpCode;
Expand All @@ -673,6 +663,10 @@ public final class dev/kord/voice/gateway/OpCode : java/lang/Enum {
public static fun values ()[Ldev/kord/voice/gateway/OpCode;
}

public final class dev/kord/voice/gateway/OpCode$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}

public final class dev/kord/voice/gateway/Ready : dev/kord/voice/gateway/VoiceEvent {
public static final field Companion Ldev/kord/voice/gateway/Ready$Companion;
public synthetic fun <init> (ILjava/lang/String;ILjava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
12 changes: 6 additions & 6 deletions voice/src/main/kotlin/gateway/Command.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import kotlinx.serialization.SerializationStrategy as KSerializationStrategy
public sealed class Command {
public object SerializationStrategy : KSerializationStrategy<Command> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Command") {
element("op", OpCode.Serializer.descriptor)
element("op", OpCode.serializer().descriptor)
element("d", JsonObject.serializer().descriptor)
}

Expand All @@ -24,23 +24,23 @@ public sealed class Command {

when (value) {
is Identify -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Identify)
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Identify)
composite.encodeSerializableElement(descriptor, 1, Identify.serializer(), value)
}
is Heartbeat -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Heartbeat)
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Heartbeat)
composite.encodeLongElement(descriptor, 1, value.nonce)
}
is SendSpeaking -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Speaking)
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Speaking)
composite.encodeSerializableElement(descriptor, 1, SendSpeaking.serializer(), value)
}
is SelectProtocol -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.SelectProtocol)
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.SelectProtocol)
composite.encodeSerializableElement(descriptor, 1, SelectProtocol.serializer(), value)
}
is Resume -> {
composite.encodeSerializableElement(descriptor, 0, OpCode.Serializer, OpCode.Resume)
composite.encodeSerializableElement(descriptor, 0, OpCode.serializer(), OpCode.Resume)
composite.encodeSerializableElement(descriptor, 1, Resume.serializer(), value)
}
}
Expand Down
6 changes: 3 additions & 3 deletions voice/src/main/kotlin/gateway/OpCode.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package dev.kord.voice.gateway

import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
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

@Serializable(with = OpCode.Serializer::class)
public enum class OpCode(public val code: Int) {
Unknown(Int.MIN_VALUE),
Identify(0),
Expand All @@ -22,8 +23,7 @@ public enum class OpCode(public val code: Int) {
ClientDisconnect(13);

internal object Serializer : KSerializer<OpCode> {
override val descriptor: SerialDescriptor
get() = PrimitiveSerialDescriptor("op", PrimitiveKind.INT)
override val descriptor = PrimitiveSerialDescriptor("dev.kord.voice.gateway.OpCode", PrimitiveKind.INT)

private val entriesByCode = entries.associateBy { it.code }
override fun deserialize(decoder: Decoder): OpCode {
Expand Down
124 changes: 73 additions & 51 deletions voice/src/main/kotlin/gateway/VoiceEvent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,71 @@ import dev.kord.common.entity.Snowflake
import dev.kord.voice.EncryptionMode
import dev.kord.voice.SpeakingFlags
import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.nullable
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.CompositeDecoder
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import kotlinx.serialization.encoding.decodeStructure
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.DeserializationStrategy as KDeserializationStrategy

private val jsonLogger = KotlinLogging.logger { }

public sealed class VoiceEvent {
public object DeserializationStrategy : KDeserializationStrategy<VoiceEvent?> {
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("Event") {
element("op", OpCode.Serializer.descriptor)
element("d", JsonElement.serializer().descriptor)
override val descriptor: SerialDescriptor = buildClassSerialDescriptor("dev.kord.voice.gateway.Event") {
element("op", OpCode.serializer().descriptor)
element("d", JsonElement.serializer().descriptor, isOptional = true)
}

@OptIn(ExperimentalSerializationApi::class)
override fun deserialize(decoder: Decoder): VoiceEvent? {
override fun deserialize(decoder: Decoder): VoiceEvent? = decoder.decodeStructure(descriptor) {
var op: OpCode? = null
var data: VoiceEvent? = null

with(decoder.beginStructure(descriptor)) {
loop@ while (true) {
when (val index = decodeElementIndex(descriptor)) {
CompositeDecoder.DECODE_DONE -> break@loop
0 -> op = OpCode.Serializer.deserialize(decoder)
1 -> data = when (op) {
OpCode.Hello -> decodeSerializableElement(
descriptor,
index,
Hello.serializer()
)
OpCode.HeartbeatAck -> {
HeartbeatAck(decodeInlineElement(HeartbeatAck.serializer().descriptor, 0).decodeLong())
}
OpCode.Ready -> decodeSerializableElement(descriptor, index, Ready.serializer())
OpCode.SessionDescription -> decodeSerializableElement(
descriptor,
index,
SessionDescription.serializer()
)
OpCode.Speaking -> decodeSerializableElement(descriptor, index, Speaking.serializer())
OpCode.Resumed -> Resumed
else -> {
val element = decodeNullableSerializableElement(
descriptor,
index,
JsonElement.serializer().nullable
)

jsonLogger.debug { "Unknown event with Opcode $op : $element" }
null
}
}
}
var d: JsonElement? = null
while (true) {
when (val index = decodeElementIndex(descriptor)) {
0 -> op = decodeSerializableElement(descriptor, index, OpCode.serializer(), op)
1 -> d = decodeSerializableElement(descriptor, index, JsonElement.serializer(), d)
CompositeDecoder.DECODE_DONE -> break
else -> throw SerializationException("Unexpected index: $index")
}
}
when (op) {
null ->
throw @OptIn(ExperimentalSerializationApi::class) MissingFieldException("op", descriptor.serialName)
OpCode.Ready -> decodeEvent(decoder, op, Ready.serializer(), d)
OpCode.SessionDescription -> decodeEvent(decoder, op, SessionDescription.serializer(), d)
OpCode.Speaking -> decodeEvent(decoder, op, Speaking.serializer(), d)
OpCode.HeartbeatAck -> decodeEvent(decoder, op, HeartbeatAck.serializer(), d)
OpCode.Hello -> decodeEvent(decoder, op, Hello.serializer(), d)
OpCode.Resumed -> {
// ignore the d field, Resumed is supposed to have null here:
// https://discord.com/developers/docs/topics/voice-connections#resuming-voice-connection-example-resumed-payload
Resumed
}
OpCode.Identify, OpCode.SelectProtocol, OpCode.Heartbeat, OpCode.Resume, OpCode.ClientDisconnect,
OpCode.Unknown,
-> {
jsonLogger.debug { "Unknown voice gateway event with opcode $op : $d" }
null
}
endStructure(descriptor)
return data
}
}

private fun <T> decodeEvent(
decoder: Decoder,
op: OpCode,
deserializer: KDeserializationStrategy<T>,
d: JsonElement?,
): T {
requireNotNull(d) { "Voice gateway event is missing 'd' field for opcode $op" }
// this cast will always succeed, otherwise decoder couldn't have decoded d
return (decoder as JsonDecoder).json.decodeFromJsonElement(deserializer, d)
}
}
}

Expand All @@ -87,8 +88,14 @@ public data class Hello(
val heartbeatInterval: Double
) : VoiceEvent()

@Serializable
public data class HeartbeatAck(val nonce: Long) : VoiceEvent()
@Serializable(with = HeartbeatAck.Serializer::class)
public data class HeartbeatAck(val nonce: Long) : VoiceEvent() {
internal object Serializer : KSerializer<HeartbeatAck> {
override val descriptor = PrimitiveSerialDescriptor("dev.kord.voice.gateway.HeartbeatAck", PrimitiveKind.LONG)
override fun serialize(encoder: Encoder, value: HeartbeatAck) = encoder.encodeLong(value.nonce)
override fun deserialize(decoder: Decoder) = HeartbeatAck(nonce = decoder.decodeLong())
}
}

@Serializable
public data class SessionDescription(
Expand All @@ -105,8 +112,23 @@ public data class Speaking(
val speaking: SpeakingFlags
) : VoiceEvent()

@Serializable
public object Resumed : VoiceEvent()
public object Resumed : VoiceEvent() {
@Deprecated(
"'Resumed' is no longer serializable, deserialize it with 'VoiceEvent.DeserializationStrategy' instead. " +
"Deprecated without a replacement.",
level = DeprecationLevel.WARNING,
)
public fun serializer(): KSerializer<Resumed> = serializer

private val serializer: KSerializer<Resumed> by lazy(LazyThreadSafetyMode.PUBLICATION) {
@Suppress("INVISIBLE_MEMBER")
kotlinx.serialization.internal.ObjectSerializer(
serialName = "dev.kord.voice.gateway.Resumed",
objectInstance = Resumed,
classAnnotations = arrayOf(),
)
}
}

public sealed class Close : VoiceEvent() {
/**
Expand Down