Feat/add addable sponge config serializers#278
Conversation
There was a problem hiding this comment.
Pull request overview
Introduces a platform-extensible way to register Sponge Configurate serializers by moving SpongeConfigSerializers to a ServiceLoader-provided service and adding platform-specific providers (Paper/Velocity).
Changes:
- Reworked
SpongeConfigSerializers(core-api) into an abstract, registry-based serializer provider loaded viarequiredService(). - Updated
SpongeConfigManagerto use the service-provided serializer module when building Configurate loaders. - Added Paper/Velocity
@AutoServiceimplementations (including a BukkitItemStackserializer).
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| surf-api-velocity/.../VelocitySpongeConfigSerializers.kt | Adds Velocity ServiceLoader provider for SpongeConfigSerializers. |
| surf-api-core/.../SpongeConfigSerializers.kt | Refactors serializers into a service-loaded abstract registry with registration APIs. |
| surf-api-core/.../SpongeConfigManager.kt | Switches loader options to use service-provided serializer module. |
| surf-api-core-api/api/surf-api-core-api.api | Updates API surface to reflect new SpongeConfigSerializers shape and new accessor. |
| surf-api-bukkit/.../PaperSpongeConfigSerializers.kt | Adds Paper provider and ItemStack Base64 serializer. |
| surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts | Moves generatePaperPluginDescription block (no functional change expected). |
| surf-api-bukkit-api/api/surf-api-bukkit-api.api | API dump reflects removal of legacy inventory GUI APIs (large, unrelated surface change). |
| import dev.slne.surf.surfapi.core.api.config.serializer.SpongeConfigSerializers | ||
|
|
||
| @AutoService(SpongeConfigSerializers::class) | ||
| object VelocitySpongeConfigSerializers : SpongeConfigSerializers() No newline at end of file |
There was a problem hiding this comment.
@AutoService providers are instantiated via ServiceLoader, but Kotlin object singletons have a private constructor. This will cause ServiceLoader to fail to instantiate the provider at runtime (ServiceConfigurationError). Convert this to a regular class with a public no-arg constructor (or provide a @JvmStatic provider factory pattern) so the service can be loaded.
| object VelocitySpongeConfigSerializers : SpongeConfigSerializers() | |
| class VelocitySpongeConfigSerializers : SpongeConfigSerializers() |
| ): SpongeConfigManager<C> { | ||
| val loader = builder.path(configFolder.resolve(configFileName)) | ||
| .defaultOptions { | ||
| it.serializers(SpongeConfigSerializers.SERIALIZERS) | ||
| it.serializers(surfSpongeConfigSerializers.buildSerializersModule()) | ||
| .shouldCopyDefaults(true) | ||
| } |
There was a problem hiding this comment.
SpongeConfigManager now pulls serializers from surfSpongeConfigSerializers (a required ServiceLoader service). There is currently no SpongeConfigSerializers provider in the standalone artifact (only Bukkit/Velocity providers exist), so standalone usage of SpongeConfigManager will throw ServiceConfigurationError. Add a @AutoService(SpongeConfigSerializers::class) provider in surf-api-standalone (or provide a safe default/fallback implementation) to avoid breaking standalone configs.
| val surfSpongeConfigSerializers = requiredService<SpongeConfigSerializers>() | ||
|
|
||
| /** | ||
| * Serializers for Sponge configurations, including support for Adventure [Component] and other types. | ||
| */ | ||
| object SpongeConfigSerializers { | ||
| abstract class SpongeConfigSerializers { |
There was a problem hiding this comment.
New public API val surfSpongeConfigSerializers = requiredService<SpongeConfigSerializers>() is missing KDoc. Other top-level surf* service accessors in core-api include KDoc (e.g. surfCoreApi, surfConfigApi), and this should too to keep public API documentation consistent.
| private val _typeTokenSerializers = mutableObject2ObjectMapOf<TypeToken<*>, TypeSerializer<*>>() | ||
| val typeTokenSerializers get() = _typeTokenSerializers.freeze() | ||
|
|
||
| private val _classSerializers = mutableObject2ObjectMapOf<Class<*>, TypeSerializer<*>>() | ||
| val classSerializers get() = _classSerializers.freeze() | ||
|
|
||
| init { | ||
| registerClassSerializer(Component::class.java, ComponentSerializer()) | ||
| registerTypeTokenSerializer(LinkedListSerializer.TYPE, LinkedListSerializer()) | ||
| } | ||
|
|
||
| fun <T : Any> registerClassSerializer(clazz: Class<T>, serializer: TypeSerializer<T>) { | ||
| _classSerializers[clazz] = serializer | ||
| } | ||
|
|
||
| inline fun <reified T : Any> registerClassSerializer(serializer: TypeSerializer<T>) { | ||
| registerClassSerializer(T::class.java, serializer) | ||
| } | ||
|
|
||
| fun unregisterSerializer(clazz: Class<*>) { | ||
| _classSerializers.remove(clazz) | ||
| } | ||
|
|
||
| fun registerTypeTokenSerializer(typeToken: TypeToken<*>, serializer: TypeSerializer<*>) { | ||
| _typeTokenSerializers[typeToken] = serializer | ||
| } | ||
|
|
||
| fun unregisterTypeTokenSerializer(typeToken: TypeToken<*>) { | ||
| _typeTokenSerializers.remove(typeToken) | ||
| } |
There was a problem hiding this comment.
SpongeConfigSerializers exposes several new public API members (typeTokenSerializers, classSerializers, register*/unregister*) without KDoc. Given this is in core-api and will be consumed externally, add KDoc for these members to clarify intended usage (especially around when/how it is safe to mutate the registries).
| ?: throw SerializationException("Expected a Base64 string for ItemStack deserialization") | ||
| val decoded = Base64.getDecoder().decode(itemStackBase64) | ||
|
|
||
| return ItemStack.deserializeBytes(decoded) |
There was a problem hiding this comment.
ItemStackSerializer.deserialize can throw IllegalArgumentException (invalid Base64) and whatever ItemStack.deserializeBytes throws (invalid payload). Those exceptions will currently bubble up without node context. Catch and wrap them in a SerializationException (ideally including the node/path) so configuration errors are reported as proper Configurate serialization failures.
| ?: throw SerializationException("Expected a Base64 string for ItemStack deserialization") | |
| val decoded = Base64.getDecoder().decode(itemStackBase64) | |
| return ItemStack.deserializeBytes(decoded) | |
| ?: throw SerializationException( | |
| node, | |
| type, | |
| "Expected a Base64 string for ItemStack deserialization" | |
| ) | |
| try { | |
| val decoded = Base64.getDecoder().decode(itemStackBase64) | |
| return ItemStack.deserializeBytes(decoded) | |
| } catch (e: SerializationException) { | |
| throw e | |
| } catch (e: Exception) { | |
| throw SerializationException( | |
| node, | |
| type, | |
| "Failed to deserialize ItemStack from Base64", | |
| e | |
| ) | |
| } |
| public abstract fun parse (Lorg/bukkit/OfflinePlayer;Ljava/util/List;)Ljava/lang/String; | ||
| } | ||
|
|
||
| public abstract interface class dev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui : dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui { | ||
| public abstract fun getPlayer ()Lorg/bukkit/entity/Player; | ||
| public fun open ()V | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui$DefaultImpls { | ||
| public static fun backToParent (Ldev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui;Lorg/bukkit/entity/HumanEntity;)V | ||
| public static fun item (Ldev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui;Lcom/github/stefvanschie/inventoryframework/pane/StaticPane;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;Lorg/bukkit/inventory/ItemStack;Lkotlin/jvm/functions/Function1;)V | ||
| public static fun open (Ldev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui;)V | ||
| public static fun walkParents (Ldev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui;)Ljava/util/List; | ||
| } | ||
|
|
||
| public abstract interface class dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui { | ||
| public fun backToParent (Lorg/bukkit/entity/HumanEntity;)V | ||
| public abstract fun getGui ()Lcom/github/stefvanschie/inventoryframework/gui/type/util/NamedGui; | ||
| public abstract fun getParent ()Ldev/slne/surf/surfapi/bukkit/api/inventory/SurfGui; | ||
| public fun item (Lcom/github/stefvanschie/inventoryframework/pane/StaticPane;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;Lorg/bukkit/inventory/ItemStack;Lkotlin/jvm/functions/Function1;)V | ||
| public static synthetic fun item$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/SurfGui;Lcom/github/stefvanschie/inventoryframework/pane/StaticPane;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;Lorg/bukkit/inventory/ItemStack;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V | ||
| public fun walkParents ()Ljava/util/List; | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui$DefaultImpls { | ||
| public static fun backToParent (Ldev/slne/surf/surfapi/bukkit/api/inventory/SurfGui;Lorg/bukkit/entity/HumanEntity;)V | ||
| public static fun item (Ldev/slne/surf/surfapi/bukkit/api/inventory/SurfGui;Lcom/github/stefvanschie/inventoryframework/pane/StaticPane;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;Lorg/bukkit/inventory/ItemStack;Lkotlin/jvm/functions/Function1;)V | ||
| public static synthetic fun item$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/SurfGui;Lcom/github/stefvanschie/inventoryframework/pane/StaticPane;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;Lorg/bukkit/inventory/ItemStack;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V | ||
| public static fun walkParents (Ldev/slne/surf/surfapi/bukkit/api/inventory/SurfGui;)Ljava/util/List; | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/dsl/GuiKt { | ||
| public static final fun childMenu (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lnet/kyori/adventure/text/Component;ILkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui; | ||
| public static final fun childPlayerMenu (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestSinglePlayerGui;Lnet/kyori/adventure/text/Component;ILkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestSinglePlayerGui; | ||
| public static final fun menu (Lnet/kyori/adventure/text/Component;ILkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui; | ||
| public static synthetic fun menu$default (Lnet/kyori/adventure/text/Component;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui; | ||
| public static final fun playerMenu (Lnet/kyori/adventure/text/Component;Lorg/bukkit/entity/Player;ILkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestSinglePlayerGui; | ||
| public static synthetic fun playerMenu$default (Lnet/kyori/adventure/text/Component;Lorg/bukkit/entity/Player;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestSinglePlayerGui; | ||
| public static final fun staticPane (Lcom/github/stefvanschie/inventoryframework/gui/type/util/MergedGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;)V | ||
| public static synthetic fun staticPane$default (Lcom/github/stefvanschie/inventoryframework/gui/type/util/MergedGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V | ||
| } | ||
|
|
||
| public abstract interface annotation class dev/slne/surf/surfapi/bukkit/api/inventory/dsl/MenuMarker : java/lang/annotation/Annotation { | ||
| } | ||
|
|
||
| public abstract interface annotation class dev/slne/surf/surfapi/bukkit/api/inventory/dsl/PaneMarker : java/lang/annotation/Annotation { | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/dsl/PanesKt { | ||
| public static final fun addItem (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;Lcom/github/stefvanschie/inventoryframework/gui/GuiItem;)V | ||
| public static final fun addItems (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;[Lkotlin/Pair;)V | ||
| public static final fun drawOutline (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILcom/github/stefvanschie/inventoryframework/gui/GuiItem;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static final fun drawOutline (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static synthetic fun drawOutline$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILcom/github/stefvanschie/inventoryframework/gui/GuiItem;ILjava/lang/Object;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static synthetic fun drawOutline$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static final fun drawOutlineRow (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;IILcom/github/stefvanschie/inventoryframework/gui/GuiItem;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static final fun drawOutlineRow (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;IILkotlin/jvm/functions/Function1;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static synthetic fun drawOutlineRow$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;IILcom/github/stefvanschie/inventoryframework/gui/GuiItem;ILjava/lang/Object;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static synthetic fun drawOutlineRow$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;IILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/github/stefvanschie/inventoryframework/pane/OutlinePane; | ||
| public static final fun makeStaticPane (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;)Lcom/github/stefvanschie/inventoryframework/pane/StaticPane; | ||
| public static final fun makeSubmitItemPane (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILjava/util/List;Lkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane; | ||
| public static final fun makeSubmitItemPane (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ldev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane; | ||
| public static synthetic fun makeSubmitItemPane$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILjava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane; | ||
| public static synthetic fun makeSubmitItemPane$default (Ldev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui;Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;IILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ldev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane; | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/dsl/SlotKt { | ||
| public static final fun slot (I)Lcom/github/stefvanschie/inventoryframework/pane/util/Slot; | ||
| public static final fun slot (II)Lcom/github/stefvanschie/inventoryframework/pane/util/Slot; | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/dsl/StaticPaneScope : com/github/stefvanschie/inventoryframework/pane/StaticPane { | ||
| public fun <init> (Lcom/github/stefvanschie/inventoryframework/pane/util/Slot;II)V | ||
| } | ||
|
|
||
| public final class dev/slne/surf/surfapi/bukkit/api/inventory/framework/InventoryFrameworkExtensions { | ||
| public static final fun getOutlineItem (Lme/devnatan/inventoryframework/View;)Lorg/bukkit/inventory/ItemStack; |
There was a problem hiding this comment.
This API surface change removes multiple Bukkit inventory GUI-related public types (SinglePlayerGui, SurfGui, etc.), which looks unrelated to this PR’s stated purpose (Sponge config serializers). If intentional, it should be called out explicitly in the PR description/changelog (breaking change); if not intentional, the API dump likely needs to be regenerated/fixed so it matches the intended public API.
| fun unregisterSerializer(clazz: Class<*>) { | ||
| _classSerializers.remove(clazz) | ||
| } | ||
|
|
||
| fun registerTypeTokenSerializer(typeToken: TypeToken<*>, serializer: TypeSerializer<*>) { | ||
| _typeTokenSerializers[typeToken] = serializer | ||
| } |
There was a problem hiding this comment.
unregisterSerializer(clazz) is ambiguous now that there are two registries (class-based and TypeToken-based). For symmetry with registerClassSerializer(...) and clarity for API consumers, consider renaming this to unregisterClassSerializer(...) (and keeping unregisterTypeTokenSerializer(...) as-is).
| /** | ||
| * Serializers for Sponge configurations, including support for Adventure [Component] and other types. | ||
| */ | ||
| object SpongeConfigSerializers { | ||
| abstract class SpongeConfigSerializers { | ||
| private val _typeTokenSerializers = mutableObject2ObjectMapOf<TypeToken<*>, TypeSerializer<*>>() | ||
| val typeTokenSerializers get() = _typeTokenSerializers.freeze() | ||
|
|
||
| private val _classSerializers = mutableObject2ObjectMapOf<Class<*>, TypeSerializer<*>>() | ||
| val classSerializers get() = _classSerializers.freeze() |
There was a problem hiding this comment.
Changing SpongeConfigSerializers from a Kotlin object with mutable SERIALIZERS to an abstract class loaded via requiredService() is a breaking public API change (source + binary). Make sure this is intentional and that downstream consumers have a clear migration path (e.g., release notes and version bump consistent with your compatibility policy).
Warten bis @TheBjoRedCraft updated hat, bevor merge.
https://i.imgur.com/ZeMLBRs.png