From c1742f92ac0f584a4c13fe34d0ff97c3ff24ee77 Mon Sep 17 00:00:00 2001 From: twisti <76837088+twisti-dev@users.noreply.github.com> Date: Thu, 26 Mar 2026 17:28:56 +0100 Subject: [PATCH] feat: add SerializableError class for structured error serialization --- gradle.properties | 2 +- .../api/surf-api-core-api.api | 37 ++++++++++++ .../core/api/util/SerializableError.kt | 57 +++++++++++++++++++ 3 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/SerializableError.kt diff --git a/gradle.properties b/gradle.properties index 84d0acf4..b0277016 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,6 +7,6 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=25 mcVersion=1.21.11 group=dev.slne.surf -version=1.21.11-2.70.3 +version=1.21.11-2.71.0 relocationPrefix=dev.slne.surf.surfapi.libs snapshot=false diff --git a/surf-api-core/surf-api-core-api/api/surf-api-core-api.api b/surf-api-core/surf-api-core-api/api/surf-api-core-api.api index 742d63e2..23feb198 100644 --- a/surf-api-core/surf-api-core-api/api/surf-api-core-api.api +++ b/surf-api-core/surf-api-core-api/api/surf-api-core-api.api @@ -10633,6 +10633,43 @@ public final class dev/slne/surf/surfapi/core/api/util/ParticleFactory$Companion public final fun of (Lcom/github/retrooper/packetevents/protocol/particle/type/ParticleType;Lcom/github/retrooper/packetevents/util/Vector3i;Lcom/github/retrooper/packetevents/util/Vector3i;I)Lcom/github/retrooper/packetevents/protocol/particle/Particle; } +public final class dev/slne/surf/surfapi/core/api/util/SerializableError { + public static final field Companion Ldev/slne/surf/surfapi/core/api/util/SerializableError$Companion; + public fun (Ljava/lang/String;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/util/SerializableError;)V + public synthetic fun (Ljava/lang/String;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/util/SerializableError;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun buildFakeThrowable ()Ljava/lang/Throwable; + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ldev/slne/surf/surfapi/core/api/util/SerializableError; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/util/SerializableError;)Ldev/slne/surf/surfapi/core/api/util/SerializableError; + public static synthetic fun copy$default (Ldev/slne/surf/surfapi/core/api/util/SerializableError;Ljava/lang/String;Ljava/lang/String;Ldev/slne/surf/surfapi/core/api/util/SerializableError;ILjava/lang/Object;)Ldev/slne/surf/surfapi/core/api/util/SerializableError; + public fun equals (Ljava/lang/Object;)Z + public final fun getCause ()Ldev/slne/surf/surfapi/core/api/util/SerializableError; + public final fun getMessage ()Ljava/lang/String; + public final fun getType ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final synthetic class dev/slne/surf/surfapi/core/api/util/SerializableError$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Ldev/slne/surf/surfapi/core/api/util/SerializableError$$serializer; + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ldev/slne/surf/surfapi/core/api/util/SerializableError; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Ldev/slne/surf/surfapi/core/api/util/SerializableError;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class dev/slne/surf/surfapi/core/api/util/SerializableError$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + +public final class dev/slne/surf/surfapi/core/api/util/SerializableErrorKt { + public static final fun toSerializableError (Ljava/lang/Throwable;)Ldev/slne/surf/surfapi/core/api/util/SerializableError; +} + public abstract class dev/slne/surf/surfapi/core/api/util/SurfTypeParameterMatcher { public static final field Companion Ldev/slne/surf/surfapi/core/api/util/SurfTypeParameterMatcher$Companion; public fun ()V diff --git a/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/SerializableError.kt b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/SerializableError.kt new file mode 100644 index 00000000..46a3f22e --- /dev/null +++ b/surf-api-core/surf-api-core-api/src/main/kotlin/dev/slne/surf/surfapi/core/api/util/SerializableError.kt @@ -0,0 +1,57 @@ +package dev.slne.surf.surfapi.core.api.util + +import kotlinx.serialization.Serializable + +/** + * Represents an error in a serializable structure, which can encapsulate information about + * the type, an optional message, and an optional nested cause. + * + * This class is primarily used for the structured serialization of error information and offers + * the ability to generate a throwable representation of the error. + * + * @property type The type or category of the error, describing the nature of the issue. + * @property message An optional detailed message providing more context about the error. + * @property cause An optional nested instance of `SerializableError` representing the underlying cause of the error. + */ +@Serializable +data class SerializableError( + val type: String, + val message: String? = null, + val cause: SerializableError? = null +) { + + /** + * Constructs a Throwable representation of the SerializableError instance. + * The resulting Throwable includes the error type as its primary message, optionally followed + * by the detailed message if provided. If the error has a nested cause, it recursively builds + * a Throwable for the cause as well. + * + * @return A Throwable instance representing the SerializableError, including its type, + * optional message, and optional cause. + */ + fun buildFakeThrowable(): Throwable { + val throwableMessage = buildString { + append(type) + if (!message.isNullOrBlank()) { + append(": ") + append(message) + } + } + + return RuntimeException(throwableMessage, cause?.buildFakeThrowable()) + } +} + +/** + * Converts a `Throwable` instance into a `SerializableError` representation. + * This allows for structured serialization of exception details, including the type, + * message, and nested causes of the original `Throwable`. + * + * @return A `SerializableError` containing the type of the `Throwable`, its message, + * and recursively serialized causes (if any). + */ +fun Throwable.toSerializableError(): SerializableError = SerializableError( + type = this::class.qualifiedName ?: this::class.simpleName ?: "UnknownThrowable", + message = message, + cause = cause?.toSerializableError() +) \ No newline at end of file