mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Implement type-safe config for plugins in BE and FE
This commit is contained in:
@@ -20,13 +20,4 @@ publishing {
|
||||
dependencies {
|
||||
// PF4J (shared)
|
||||
api("org.pf4j:pf4j:${rootProject.extra["pf4jVersion"]}")
|
||||
|
||||
implementation(kotlin("stdlib"))
|
||||
|
||||
// Test dependencies
|
||||
testImplementation(kotlin("test"))
|
||||
}
|
||||
|
||||
tasks.test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core
|
||||
|
||||
interface Configurable {
|
||||
val configMetadata: List<PluginConfigElement>
|
||||
var config: Map<String, String?>
|
||||
|
||||
fun validateConfig(): PluginConfigValidationResult = validateConfig(config)
|
||||
fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult
|
||||
}
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core
|
||||
|
||||
import org.pf4j.PluginWrapper
|
||||
|
||||
abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
|
||||
|
||||
companion object {
|
||||
lateinit var plugin: ConfigurableGameyfinPlugin
|
||||
private set
|
||||
}
|
||||
|
||||
init {
|
||||
plugin = this
|
||||
}
|
||||
|
||||
override var config: Map<String, String?> = emptyMap()
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core
|
||||
|
||||
data class PluginConfigElement(
|
||||
val key: String,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val isSecret: Boolean = false
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core.config
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
typealias PluginConfigMetadata = List<ConfigMetadata<*>>
|
||||
|
||||
data class ConfigMetadata<T : Serializable>(
|
||||
val key: String,
|
||||
val type: Class<T>,
|
||||
val label: String,
|
||||
val description: String,
|
||||
val default: T? = null,
|
||||
val isSecret: Boolean = false,
|
||||
val isRequired: Boolean = true,
|
||||
) {
|
||||
var allowedValues: List<T>? = null
|
||||
|
||||
init {
|
||||
allowedValues = type.enumConstants?.toList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core.config
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
interface Configurable {
|
||||
val configMetadata: PluginConfigMetadata
|
||||
|
||||
fun loadConfig(config: Map<String, String?>)
|
||||
|
||||
fun validateConfig(): PluginConfigValidationResult
|
||||
fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult
|
||||
|
||||
fun <T : Serializable> config(key: String): T
|
||||
fun <T : Serializable> optionalConfig(key: String): T?
|
||||
}
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core
|
||||
package de.grimsi.gameyfin.pluginapi.core.config
|
||||
|
||||
class PluginConfigError(message: String) : RuntimeException(message)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core
|
||||
package de.grimsi.gameyfin.pluginapi.core.config
|
||||
|
||||
data class PluginConfigValidationResult(
|
||||
val result: PluginConfigValidationResultType,
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core.wrapper
|
||||
|
||||
import de.grimsi.gameyfin.pluginapi.core.config.ConfigMetadata
|
||||
import de.grimsi.gameyfin.pluginapi.core.config.Configurable
|
||||
import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigError
|
||||
import de.grimsi.gameyfin.pluginapi.core.config.PluginConfigValidationResult
|
||||
import org.pf4j.PluginWrapper
|
||||
import java.io.Serializable
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
abstract class ConfigurableGameyfinPlugin(wrapper: PluginWrapper) : GameyfinPlugin(wrapper), Configurable {
|
||||
|
||||
private var config: Map<String, String?> = emptyMap()
|
||||
|
||||
override fun loadConfig(config: Map<String, String?>) {
|
||||
this.config = config
|
||||
}
|
||||
|
||||
override fun validateConfig(): PluginConfigValidationResult = validateConfig(config)
|
||||
|
||||
override fun validateConfig(config: Map<String, String?>): PluginConfigValidationResult {
|
||||
val errors = mutableMapOf<String, String>()
|
||||
|
||||
for (meta in configMetadata) {
|
||||
val value = resolveValue(meta.key, config)
|
||||
if (meta.isRequired && value == null) {
|
||||
errors[meta.key] = "${meta.label} is required"
|
||||
continue
|
||||
}
|
||||
if (value != null) {
|
||||
try {
|
||||
castConfigValue(meta, value)
|
||||
} catch (e: PluginConfigError) {
|
||||
errors[meta.key] = e.message ?: "Invalid value"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return if (errors.isEmpty()) {
|
||||
PluginConfigValidationResult.VALID
|
||||
} else {
|
||||
PluginConfigValidationResult.INVALID(errors)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Serializable> optionalConfig(key: String): T? {
|
||||
val meta = resolveMetadata(key)
|
||||
val value = resolveValue(key)
|
||||
if (value == null) return null
|
||||
|
||||
return try {
|
||||
castConfigValue(meta, value) as T
|
||||
} catch (e: Exception) {
|
||||
throw PluginConfigError("Failed to cast value for key '$key' to type ${meta.type.simpleName}: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun castConfigValue(meta: ConfigMetadata<*>, value: Any): Any? {
|
||||
val expectedType = meta.type
|
||||
return if (expectedType.isEnum) {
|
||||
try {
|
||||
java.lang.Enum.valueOf(expectedType as Class<out Enum<*>>, value.toString())
|
||||
} catch (_: IllegalArgumentException) {
|
||||
throw PluginConfigError("Invalid value '${value}', must be one of ${meta.allowedValues!!.joinToString(", ")}")
|
||||
}
|
||||
} else {
|
||||
if (!expectedType.isInstance(value)) {
|
||||
throw PluginConfigError("Value for key '${meta.key}' is not of type ${expectedType.simpleName}")
|
||||
}
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Serializable> config(key: String): T {
|
||||
val value = optionalConfig<T>(key)
|
||||
if (value == null) {
|
||||
throw PluginConfigError("Required configuration key '$key' is missing or has no value")
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
private fun resolveMetadata(key: String): ConfigMetadata<*> {
|
||||
return configMetadata.find { it.key == key }
|
||||
?: throw PluginConfigError("Unknown configuration key: $key")
|
||||
}
|
||||
|
||||
private fun resolveValue(key: String, configOverride: Map<String, Serializable?>? = null): Serializable? {
|
||||
val meta = resolveMetadata(key)
|
||||
val conf = configOverride ?: config
|
||||
return conf[key] ?: meta.default
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -1,8 +1,9 @@
|
||||
package de.grimsi.gameyfin.pluginapi.core
|
||||
package de.grimsi.gameyfin.pluginapi.core.wrapper
|
||||
|
||||
import org.pf4j.Plugin
|
||||
import org.pf4j.PluginWrapper
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
abstract class GameyfinPlugin(wrapper: PluginWrapper) : Plugin(wrapper) {
|
||||
|
||||
companion object {
|
||||
Reference in New Issue
Block a user