From 7cec13eb06b5d7d21bf590f18b29f9474e7d58ed Mon Sep 17 00:00:00 2001 From: Hendrik Brombeer Date: Wed, 6 May 2026 20:28:43 +0200 Subject: [PATCH] fix(whitelist): switch Moshi to codegen so kotlin-reflect drops out of the JAR Plugin onEnable crashed with: IllegalStateException: Resource not found in classpath: gg/grounds/platform/shaded/kotlin/gg.grounds.platform.shaded.kotlin.gg.grounds.platform.shaded.kotlin_builtins ShadowJar's `relocate("kotlin", "gg.grounds.platform.shaded.kotlin")` rewrites the path *and content* of every .kotlin_builtins resource, producing the triple-prefixed garbage path above. Moshi's KotlinJsonAdapterFactory (via moshi-kotlin) needs kotlin-reflect to read those resources at runtime, so adapter() throws ExceptionInInitializerError as soon as the WhitelistApiClient constructor runs. Whitelist sync never starts; the entire plugin gets disabled by Paper. Drop the reflection path entirely: - swap moshi-kotlin runtime dep for moshi-kotlin-codegen via KSP - annotate ListResponse + RawEntry with @JsonClass(generateAdapter = true) - remove KotlinJsonAdapterFactory from the Moshi.Builder KSP generates ListResponseJsonAdapter at compile time. No kotlin-reflect on the runtime classpath, so no relocation landmine. Same external behaviour, just deserialised by generated code instead of reflective class scanning. --- build.gradle.kts | 11 ++++++++++- .../platform/whitelist/WhitelistApiClient.kt | 17 +++++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2c52711..f3dc994 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,6 +4,14 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "2.2.20" + // KSP for moshi-kotlin-codegen — generates JsonAdapter classes at + // compile time so the shaded JAR doesn't have to ship kotlin-reflect. + // Reflection-based KotlinJsonAdapterFactory + ShadowJar's `relocate(kotlin)` + // produced corrupted paths inside the .kotlin_builtins resources + // (`gg/grounds/platform/shaded/kotlin/gg.grounds.platform.shaded.kotlin.…_builtins`) + // and crashed Moshi's adapter() at plugin onEnable. Codegen sidesteps the + // whole reflection path. + id("com.google.devtools.ksp") version "2.2.20-2.0.4" id("com.gradleup.shadow") version "9.4.1" id("com.diffplug.spotless") version "8.4.0" } @@ -32,7 +40,8 @@ dependencies { // we get back from forge. Avoids pulling in jackson / gson which // would inflate the shadow jar dramatically for two DTOs. implementation("com.squareup.moshi:moshi:1.15.2") - implementation("com.squareup.moshi:moshi-kotlin:1.15.2") + // moshi-kotlin (reflection) replaced with codegen — see plugins block. + ksp("com.squareup.moshi:moshi-kotlin-codegen:1.15.2") testImplementation("org.junit.jupiter:junit-jupiter:5.11.3") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/src/main/kotlin/gg/grounds/platform/whitelist/WhitelistApiClient.kt b/src/main/kotlin/gg/grounds/platform/whitelist/WhitelistApiClient.kt index 568f466..4e5da76 100644 --- a/src/main/kotlin/gg/grounds/platform/whitelist/WhitelistApiClient.kt +++ b/src/main/kotlin/gg/grounds/platform/whitelist/WhitelistApiClient.kt @@ -1,9 +1,8 @@ package gg.grounds.platform.whitelist import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonClass import com.squareup.moshi.Moshi -import com.squareup.moshi.Types -import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest @@ -33,8 +32,12 @@ class WhitelistApiClient( HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(), ) { + // Codegen-backed adapter: KSP generates ListResponseJsonAdapter at + // compile time, no kotlin-reflect on the runtime classpath. Mandatory + // because shadowJar's `relocate(kotlin)` corrupts the .kotlin_builtins + // resources that the reflection-based adapter loads. private val adapter: JsonAdapter = - Moshi.Builder().add(KotlinJsonAdapterFactory()).build().adapter(ListResponse::class.java) + Moshi.Builder().build().adapter(ListResponse::class.java) /** * Fetches the current whitelist. Throws on any non-2xx — caller decides whether to surface or @@ -69,11 +72,9 @@ class WhitelistApiClient( return parsed.items.map { WhitelistEntry(mcUuid = it.mcUuid, mcUsername = it.mcUsername) } } - private data class ListResponse(val items: List) { + @JsonClass(generateAdapter = true) + internal data class ListResponse(val items: List) { + @JsonClass(generateAdapter = true) data class RawEntry(val mcUuid: String, val mcUsername: String) } - - @Suppress("unused") - private val typeRef = - Types.newParameterizedType(List::class.java, ListResponse.RawEntry::class.java) }