From 9fad9da76a8bf9e09f0d371a48b29fac86a44696 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 12:08:36 +0100 Subject: [PATCH 1/7] fix: Authorize custom configuration reader --- build.gradle.kts | 1 + .../com/github/rushyverse/api/RushyServer.kt | 39 ++++++-- .../api/configuration/ConfigurationReader.kt | 27 ++++++ .../configuration/HoconConfigurationReader.kt | 51 +++++++++++ .../api/configuration/IConfiguration.kt | 18 +--- .../com/github/rushyverse/api/AbstractTest.kt | 2 +- .../github/rushyverse/api/RushyServerTest.kt | 8 +- .../HoconConfigurationReaderTest.kt | 90 +++++++++++++++++++ .../api/configuration/IConfigurationTest.kt | 34 +------ src/test/resources/server.conf | 2 +- 10 files changed, 210 insertions(+), 62 deletions(-) create mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt create mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index db342eaa..c6a512ad 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -50,6 +50,7 @@ kotlin { languageSettings { optIn("kotlin.RequiresOptIn") optIn("kotlin.ExperimentalStdlibApi") + optIn("kotlinx.serialization.ExperimentalSerializationApi") optIn("kotlin.contracts.ExperimentalContracts") optIn("kotlinx.coroutines.ExperimentalCoroutinesApi") } diff --git a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt index fb637c98..9f988912 100644 --- a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt @@ -4,10 +4,9 @@ import com.github.rushyverse.api.command.GamemodeCommand import com.github.rushyverse.api.command.GiveCommand import com.github.rushyverse.api.command.KickCommand import com.github.rushyverse.api.command.StopCommand -import com.github.rushyverse.api.configuration.IBungeeCordConfiguration -import com.github.rushyverse.api.configuration.IConfiguration -import com.github.rushyverse.api.configuration.IVelocityConfiguration +import com.github.rushyverse.api.configuration.* import com.github.rushyverse.api.translation.ResourceBundleTranslationsProvider +import com.github.rushyverse.api.translation.SupportedLanguage import com.github.rushyverse.api.translation.TranslationsProvider import com.github.rushyverse.api.translation.registerResourceBundleForSupportedLocales import com.github.rushyverse.api.utils.workingDirectory @@ -22,6 +21,7 @@ import net.minestom.server.instance.AnvilLoader import net.minestom.server.instance.InstanceContainer import java.io.File import java.util.* +import kotlin.reflect.KClass public val logger: KLogger = KotlinLogging.logger { } @@ -60,10 +60,11 @@ public abstract class RushyServer { */ protected suspend inline fun start( configurationPath: String? = null, + configurationReader: ConfigurationReader = HoconConfigurationReader(), init: T.(InstanceContainer) -> Unit ) { logger.info { "Loading configuration from $configurationPath" } - val config = loadConfiguration(configurationPath) + val config = loadConfiguration(configurationReader, configurationPath) logger.info { "Configuration loaded" } val minecraftServer = MinecraftServer.init() @@ -82,6 +83,10 @@ public abstract class RushyServer { minecraftServer.start("0.0.0.0", serverConfig.port) } + /** + * Enable the online mode if [enabled] is true. + * @param enabled If the online mode should be enabled. + */ protected open suspend fun applyOnlineMode(enabled: Boolean) { if (enabled) { logger.info { "Enabling Online mode" } @@ -110,7 +115,7 @@ public abstract class RushyServer { if (bungeeCord.enabled) { logger.info { "Enabling BungeeCord support" } BungeeCordProxy.enable() - BungeeCordProxy.setBungeeGuardTokens(setOf(bungeeCord.secret)) + BungeeCordProxy.setBungeeGuardTokens(bungeeCord.secrets) logger.info { "BungeeCord support enabled" } } } @@ -134,16 +139,36 @@ public abstract class RushyServer { /** * Load the configuration using the file or the default config file. + * @param configurationReader Configuration reader to use. + * @param configFile Path of the configuration file. + * @return The configuration of the server. + */ + protected suspend inline fun loadConfiguration( + configurationReader: ConfigurationReader, + configFile: String? + ): T { + return loadConfiguration(T::class, configurationReader, configFile) + } + + /** + * Load the configuration using the file or the default config file. + * @param clazz Type of configuration class to load. + * @param configurationReader Configuration reader to use. * @param configFile Path of the configuration file. * @return The configuration of the server. */ - protected inline fun loadConfiguration(configFile: String?): T { + protected open suspend fun loadConfiguration( + clazz: KClass, + configurationReader: ConfigurationReader, + configFile: String? + ): T { val configurationFile = IConfiguration.getOrCreateConfigurationFile(configFile) - return IConfiguration.readHoconConfigurationFile(configurationFile) + return configurationReader.readConfigurationFile(clazz, configurationFile) } /** * Create a translation provider to provide translations for the [supported languages][SupportedLanguage]. + * @param bundles Bundles to load. * @return New translation provider. */ protected open suspend fun createTranslationsProvider(bundles: Iterable): TranslationsProvider { diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt new file mode 100644 index 00000000..7b037692 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt @@ -0,0 +1,27 @@ +package com.github.rushyverse.api.configuration + +import kotlinx.serialization.KSerializer +import java.io.File +import kotlin.reflect.KClass + +/** + * Configuration reader. + */ +public interface ConfigurationReader { + + /** + * Load the configuration from the given file. + * @param clazz Type of configuration class to load. + * @param configFile Configuration file to load. + * @return The configuration loaded from the given file. + */ + public fun readConfigurationFile(clazz: KClass, configFile: File): T + + /** + * Load the configuration from the given file. + * @param serializer Serializer to deserialize the configuration to the given type. + * @param configFile Configuration file to load. + * @return The configuration loaded from the given file. + */ + public fun readConfigurationFile(serializer: KSerializer, configFile: File): T +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt new file mode 100644 index 00000000..27e34696 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt @@ -0,0 +1,51 @@ +package com.github.rushyverse.api.configuration + +import com.github.rushyverse.api.serializer.PosSerializer +import com.typesafe.config.ConfigFactory +import kotlinx.serialization.KSerializer +import kotlinx.serialization.hocon.Hocon +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.serializer +import net.minestom.server.coordinate.Pos +import java.io.File +import kotlin.reflect.KClass +import kotlin.reflect.full.createType + +/** + * Read configuration from HOCON file. + * @receiver Configuration reader. + * @param configFile Configuration file to load. + * @return The configuration loaded from the given file. + */ +public inline fun HoconConfigurationReader.readConfigurationFile(configFile: File): T = + readConfigurationFile(hocon.serializersModule.serializer(), configFile) + +/** + * Read configuration from HOCON file. + * @property hocon [Hocon] configuration to use. + */ +public class HoconConfigurationReader(public val hocon: Hocon = hoconDefault) : ConfigurationReader { + + public companion object { + /** + * Default [Hocon] configuration using custom serializer. + * @see PosSerializer + */ + public val hoconDefault: Hocon = Hocon { + serializersModule = SerializersModule { + contextual(Pos::class, PosSerializer) + } + } + } + + override fun readConfigurationFile(clazz: KClass, configFile: File): T { + val serializer = hocon.serializersModule.serializer(clazz.createType()) + @Suppress("UNCHECKED_CAST") + return readConfigurationFile(serializer as KSerializer, configFile) + } + + override fun readConfigurationFile(serializer: KSerializer, configFile: File): T { + val config = ConfigFactory.parseFile(configFile) + return hocon.decodeFromConfig(serializer, config) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt index 427df353..76a8f2fa 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt @@ -1,10 +1,7 @@ package com.github.rushyverse.api.configuration import com.github.rushyverse.api.utils.workingDirectory -import com.typesafe.config.ConfigFactory import kotlinx.serialization.Serializable -import kotlinx.serialization.hocon.Hocon -import kotlinx.serialization.hocon.decodeFromConfig import java.io.File import java.io.FileNotFoundException @@ -66,15 +63,6 @@ public interface IConfiguration { } } } - - /** - * Load the configuration from the given file with HOCON format. - * @param configFile Configuration file to load. - * @return The configuration loaded from the given file. - */ - public inline fun readHoconConfigurationFile(configFile: File): T = - Hocon.decodeFromConfig(ConfigFactory.parseFile(configFile)) - } public val server: IServerConfiguration @@ -127,13 +115,13 @@ public data class VelocityConfiguration( /** * Configuration to connect the server to the bungeeCord proxy. * @property enabled Whether the server should connect to the bungeeCord proxy. - * @property secret Secret to verify if the client comes from the proxy. + * @property secrets Secrets to verify if the client comes from the proxy. */ public interface IBungeeCordConfiguration { public val enabled: Boolean - public val secret: String + public val secrets: Set } @@ -143,5 +131,5 @@ public interface IBungeeCordConfiguration { @Serializable public data class BungeeCordConfiguration( override val enabled: Boolean, - override val secret: String + override val secrets: Set ) : IBungeeCordConfiguration \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt index 668136cf..b481d24c 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt @@ -44,7 +44,7 @@ abstract class AbstractTest { DEFAULT_WORLD, false, VelocityConfiguration(false, ""), - BungeeCordConfiguration(false, "") + BungeeCordConfiguration(false, emptySet()) ) ) diff --git a/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt b/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt index 048d2d70..3f3151ae 100644 --- a/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt @@ -4,9 +4,7 @@ import com.github.rushyverse.api.command.GamemodeCommand import com.github.rushyverse.api.command.GiveCommand import com.github.rushyverse.api.command.KickCommand import com.github.rushyverse.api.command.StopCommand -import com.github.rushyverse.api.configuration.BungeeCordConfiguration -import com.github.rushyverse.api.configuration.IConfiguration -import com.github.rushyverse.api.configuration.VelocityConfiguration +import com.github.rushyverse.api.configuration.* import com.github.rushyverse.api.utils.randomString import kotlinx.coroutines.test.runTest import net.minestom.server.MinecraftServer @@ -54,7 +52,7 @@ class RushyServerTest : AbstractTest() { val configurationFile = fileOfTmpDirectory(IConfiguration.DEFAULT_CONFIG_FILE_NAME) assertTrue { configurationFile.isFile } - val configuration = IConfiguration.readHoconConfigurationFile(configurationFile) + val configuration = HoconConfigurationReader().readConfigurationFile(configurationFile) assertEquals(expectedDefaultConfiguration, configuration) } @@ -180,7 +178,7 @@ class RushyServerTest : AbstractTest() { val defaultConfiguration = expectedDefaultConfiguration val configuration = expectedDefaultConfiguration.copy( server = defaultConfiguration.server.copy( - bungeeCord = BungeeCordConfiguration(enabled, secret) + bungeeCord = BungeeCordConfiguration(enabled, setOf(secret)) ) ) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt new file mode 100644 index 00000000..852539ce --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReaderTest.kt @@ -0,0 +1,90 @@ +package com.github.rushyverse.api.configuration + +import com.github.rushyverse.api.AbstractTest +import com.github.rushyverse.api.TestConfiguration +import com.typesafe.config.ConfigFactory +import kotlinx.serialization.hocon.decodeFromConfig +import net.minestom.server.coordinate.Pos +import org.junit.jupiter.api.Nested +import java.io.File +import kotlin.test.BeforeTest +import kotlin.test.Test +import kotlin.test.assertEquals + +class HoconConfigurationReaderTest : AbstractTest() { + + private lateinit var file: File + + @BeforeTest + override fun onBefore() { + super.onBefore() + file = fileOfTmpDirectory("test.conf") + } + + @Nested + inner class HoconDefaultConfiguration { + + @Test + fun `should support Pos serializer`() { + val hocon = HoconConfigurationReader.hoconDefault + + val expected = Pos(1.0, 2.0, 3.0, 4f, 5f) + file.writeText( + """ + { + x: 1.0 + y: 2.0 + z: 3.0 + yaw: 4.0 + pitch: 5.0 + } + """.trimIndent() + ) + + val configFile = ConfigFactory.parseFile(file) + val result: Pos = hocon.decodeFromConfig(configFile) + + assertEquals(expected, result) + } + + } + + @Nested + inner class ReadConfigurationFileWithReifiedTypeParameter { + + @Test + fun `should read configuration file`() { + configurationToHoconFile(expectedDefaultConfiguration, file) + val configuration = HoconConfigurationReader() + val result: TestConfiguration = configuration.readConfigurationFile(file) + assertEquals(expectedDefaultConfiguration, result) + } + + } + + @Nested + inner class ReadConfigurationFileWithClassParameter { + + @Test + fun `should read configuration file`() { + configurationToHoconFile(expectedDefaultConfiguration, file) + val configuration = HoconConfigurationReader() + val result = configuration.readConfigurationFile(TestConfiguration::class, file) + assertEquals(expectedDefaultConfiguration, result) + } + + } + + @Nested + inner class ReadConfigurationFileWithSerializerParameter { + + @Test + fun `should read configuration file`() { + configurationToHoconFile(expectedDefaultConfiguration, file) + val configuration = HoconConfigurationReader() + val result = configuration.readConfigurationFile(TestConfiguration.serializer(), file) + assertEquals(expectedDefaultConfiguration, result) + } + + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt index 6eb8d280..bfab6584 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt @@ -1,15 +1,12 @@ -@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalSerializationApi::class) +@file:OptIn(ExperimentalCoroutinesApi::class) package com.github.rushyverse.api.configuration import com.github.rushyverse.api.AbstractTest -import com.github.rushyverse.api.TestConfiguration import com.github.rushyverse.api.configuration.IConfiguration.Companion.getOrCreateConfigurationFile import com.github.rushyverse.api.utils.randomString import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.MissingFieldException import org.junit.jupiter.api.Nested import org.junit.jupiter.api.assertThrows import java.io.File @@ -93,35 +90,6 @@ class IConfigurationTest : AbstractTest() { } } - @Nested - inner class ReadHoconConfiguration { - @Test - fun `should create default configuration and read it`() = runTest { - val configurationFile = getOrCreateConfigurationFile() - - val configuration = IConfiguration.readHoconConfigurationFile(configurationFile) - assertEquals(expectedDefaultConfiguration, configuration) - } - - @Test - fun `should throw exception if file not found`() = runTest { - assertThrows { - IConfiguration.readHoconConfigurationFile(getRandomFileInTmpDirectory()) - } - } - - @Test - fun `should throw exception if fields missing`() = runTest { - val file = getRandomFileInTmpDirectory() - assertTrue { file.createNewFile() } - file.writeText("server { }") - - assertThrows { - IConfiguration.readHoconConfigurationFile(file) - } - } - } - private fun getRandomFileInTmpDirectory() = fileOfTmpDirectory(randomString()) } diff --git a/src/test/resources/server.conf b/src/test/resources/server.conf index 043462b9..2645bcaf 100644 --- a/src/test/resources/server.conf +++ b/src/test/resources/server.conf @@ -8,6 +8,6 @@ server { } bungeeCord: { enabled: false - secret: "" + secrets: [] } } \ No newline at end of file From cbd1c036123fcb4a1b4a2d9f00c2ec92f6f73ca4 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 14:09:58 +0100 Subject: [PATCH 2/7] build: Move static functions and remove parameters --- .../com/github/rushyverse/api/RushyServer.kt | 14 ++-- .../api/configuration/ConfigurationReader.kt | 27 ------ .../configuration/HoconConfigurationReader.kt | 2 +- .../api/configuration/IConfiguration.kt | 58 +------------ .../api/configuration/IConfigurationReader.kt | 84 +++++++++++++++++++ .../com/github/rushyverse/api/AbstractTest.kt | 2 +- .../github/rushyverse/api/RushyServerTest.kt | 2 +- .../api/configuration/IConfigurationTest.kt | 10 +-- .../rushyverse/api/extension/EntityExtTest.kt | 4 +- 9 files changed, 99 insertions(+), 104 deletions(-) delete mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt create mode 100644 src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt diff --git a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt index 9f988912..2620611a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt +++ b/src/main/kotlin/com/github/rushyverse/api/RushyServer.kt @@ -60,11 +60,10 @@ public abstract class RushyServer { */ protected suspend inline fun start( configurationPath: String? = null, - configurationReader: ConfigurationReader = HoconConfigurationReader(), init: T.(InstanceContainer) -> Unit ) { logger.info { "Loading configuration from $configurationPath" } - val config = loadConfiguration(configurationReader, configurationPath) + val config = loadConfiguration(configurationPath) logger.info { "Configuration loaded" } val minecraftServer = MinecraftServer.init() @@ -139,31 +138,28 @@ public abstract class RushyServer { /** * Load the configuration using the file or the default config file. - * @param configurationReader Configuration reader to use. + * Will use the [HoconConfigurationReader] to load the configuration. * @param configFile Path of the configuration file. * @return The configuration of the server. */ protected suspend inline fun loadConfiguration( - configurationReader: ConfigurationReader, configFile: String? ): T { - return loadConfiguration(T::class, configurationReader, configFile) + return loadConfiguration(T::class, configFile) } /** * Load the configuration using the file or the default config file. * @param clazz Type of configuration class to load. - * @param configurationReader Configuration reader to use. * @param configFile Path of the configuration file. * @return The configuration of the server. */ protected open suspend fun loadConfiguration( clazz: KClass, - configurationReader: ConfigurationReader, configFile: String? ): T { - val configurationFile = IConfiguration.getOrCreateConfigurationFile(configFile) - return configurationReader.readConfigurationFile(clazz, configurationFile) + val configurationFile = IConfigurationReader.getOrCreateConfigurationFile(configFile) + return HoconConfigurationReader().readConfigurationFile(clazz, configurationFile) } /** diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt deleted file mode 100644 index 7b037692..00000000 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/ConfigurationReader.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.rushyverse.api.configuration - -import kotlinx.serialization.KSerializer -import java.io.File -import kotlin.reflect.KClass - -/** - * Configuration reader. - */ -public interface ConfigurationReader { - - /** - * Load the configuration from the given file. - * @param clazz Type of configuration class to load. - * @param configFile Configuration file to load. - * @return The configuration loaded from the given file. - */ - public fun readConfigurationFile(clazz: KClass, configFile: File): T - - /** - * Load the configuration from the given file. - * @param serializer Serializer to deserialize the configuration to the given type. - * @param configFile Configuration file to load. - * @return The configuration loaded from the given file. - */ - public fun readConfigurationFile(serializer: KSerializer, configFile: File): T -} \ No newline at end of file diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt index 27e34696..e725478a 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/HoconConfigurationReader.kt @@ -24,7 +24,7 @@ public inline fun HoconConfigurationReader.readConfigurationFile(con * Read configuration from HOCON file. * @property hocon [Hocon] configuration to use. */ -public class HoconConfigurationReader(public val hocon: Hocon = hoconDefault) : ConfigurationReader { +public class HoconConfigurationReader(public val hocon: Hocon = hoconDefault) : IConfigurationReader { public companion object { /** diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt index 76a8f2fa..bd1d7bf3 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfiguration.kt @@ -1,9 +1,6 @@ package com.github.rushyverse.api.configuration -import com.github.rushyverse.api.utils.workingDirectory import kotlinx.serialization.Serializable -import java.io.File -import java.io.FileNotFoundException /** * Configuration of the application. @@ -11,61 +8,8 @@ import java.io.FileNotFoundException */ public interface IConfiguration { - public companion object { - - /** - * Default name of the config file. - * This name is used to create the default config file when the user does not provide one. - */ - public const val DEFAULT_CONFIG_FILE_NAME: String = "server.conf" - - /** - * Get the configuration file from the given path. - * If the path is null, the default config file will be used. - * If the default config file does not exist, it will be created with the default configuration from resources folder. - * @param filePath Path of the configuration file. - * @return The configuration file that must be used to load application configuration. - */ - public fun getOrCreateConfigurationFile(filePath: String? = null): File { - if (filePath != null) { - val configFile = File(filePath) - if (!configFile.isFile) { - throw FileNotFoundException("Config file $filePath does not exist or is not a regular file") - } - return configFile - } - - return getOrCreateDefaultConfigurationFile(workingDirectory) - } - - /** - * Search for the default config file in the current directory. - * If the file does not exist, it will be created with the default configuration from resources folder. - * @return The default config file. - */ - private fun getOrCreateDefaultConfigurationFile(parent: File): File = - File(parent, DEFAULT_CONFIG_FILE_NAME).apply { - if (exists()) { - return this - } - - val defaultConfiguration = - IConfiguration::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME) - ?: error("Unable to find default configuration file in server resources") - - defaultConfiguration.use { inputStream -> - if (!createNewFile()) { - throw FileSystemException(this, null, "Unable to create configuration file $absolutePath") - } - - outputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - } - } - } - public val server: IServerConfiguration + } /** diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt new file mode 100644 index 00000000..b267bc45 --- /dev/null +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt @@ -0,0 +1,84 @@ +package com.github.rushyverse.api.configuration + +import com.github.rushyverse.api.utils.workingDirectory +import kotlinx.serialization.KSerializer +import java.io.File +import java.io.FileNotFoundException +import kotlin.reflect.KClass + +/** + * Configuration reader. + */ +public interface IConfigurationReader { + + public companion object { + + /** + * Default name of the config file. + * This name is used to create the default config file when the user does not provide one. + */ + public const val DEFAULT_CONFIG_FILE_NAME: String = "server.conf" + + /** + * Get the configuration file from the given path. + * If the path is null, the default config file will be used. + * If the default config file does not exist, it will be created with the default configuration from resources folder. + * @param filePath Path of the configuration file. + * @return The configuration file that must be used to load application configuration. + */ + public fun getOrCreateConfigurationFile(filePath: String? = null): File { + if (filePath != null) { + val configFile = File(filePath) + if (!configFile.isFile) { + throw FileNotFoundException("Config file $filePath does not exist or is not a regular file") + } + return configFile + } + + return getOrCreateDefaultConfigurationFile(workingDirectory) + } + + /** + * Search for the default config file in the current directory. + * If the file does not exist, it will be created with the default configuration from resources folder. + * @param parent Parent directory of the default config file. + * @return The default config file. + */ + private fun getOrCreateDefaultConfigurationFile(parent: File): File = + File(parent, DEFAULT_CONFIG_FILE_NAME).apply { + if (exists()) { + return this + } + + val defaultConfiguration = + IConfiguration::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME) + ?: error("Unable to find default configuration file in server resources") + + defaultConfiguration.use { inputStream -> + if (!createNewFile()) { + throw FileSystemException(this, null, "Unable to create configuration file $absolutePath") + } + + outputStream().use { outputStream -> + inputStream.copyTo(outputStream) + } + } + } + } + + /** + * Load the configuration from the given file. + * @param clazz Type of configuration class to load. + * @param configFile Configuration file to load. + * @return The configuration loaded from the given file. + */ + public fun readConfigurationFile(clazz: KClass, configFile: File): T + + /** + * Load the configuration from the given file. + * @param serializer Serializer to deserialize the configuration to the given type. + * @param configFile Configuration file to load. + * @return The configuration loaded from the given file. + */ + public fun readConfigurationFile(serializer: KSerializer, configFile: File): T +} \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt b/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt index b481d24c..79e7b54f 100644 --- a/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/AbstractTest.kt @@ -66,7 +66,7 @@ abstract class AbstractTest { protected fun configurationToHoconFile( configuration: TestConfiguration, - file: File = fileOfTmpDirectory(IConfiguration.DEFAULT_CONFIG_FILE_NAME) + file: File = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) ) = file.writeText(configurationToHocon(configuration).root().render()) diff --git a/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt b/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt index 3f3151ae..c9a08cbd 100644 --- a/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/RushyServerTest.kt @@ -49,7 +49,7 @@ class RushyServerTest : AbstractTest() { assertThrows { TestServer().start() } - val configurationFile = fileOfTmpDirectory(IConfiguration.DEFAULT_CONFIG_FILE_NAME) + val configurationFile = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) assertTrue { configurationFile.isFile } val configuration = HoconConfigurationReader().readConfigurationFile(configurationFile) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt index bfab6584..0bee5cfc 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt @@ -3,7 +3,7 @@ package com.github.rushyverse.api.configuration import com.github.rushyverse.api.AbstractTest -import com.github.rushyverse.api.configuration.IConfiguration.Companion.getOrCreateConfigurationFile +import com.github.rushyverse.api.configuration.IConfigurationReader.Companion.getOrCreateConfigurationFile import com.github.rushyverse.api.utils.randomString import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -20,7 +20,7 @@ class IConfigurationTest : AbstractTest() { @Test fun `name of default configuration file is correct`() = runTest { - assertEquals("server.conf", IConfiguration.DEFAULT_CONFIG_FILE_NAME) + assertEquals("server.conf", IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) } @Nested @@ -38,7 +38,7 @@ class IConfigurationTest : AbstractTest() { @Test fun `should return the default config file without edit it`() = runTest { - createConfigFileAndCheckIfFound(IConfiguration.DEFAULT_CONFIG_FILE_NAME) { + createConfigFileAndCheckIfFound(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) { getOrCreateConfigurationFile() } } @@ -81,7 +81,7 @@ class IConfigurationTest : AbstractTest() { val configurationFile = getOrCreateConfigurationFile() assertTrue { configurationFile.isFile } - val expectedConfigurationFile = fileOfTmpDirectory(IConfiguration.DEFAULT_CONFIG_FILE_NAME) + val expectedConfigurationFile = fileOfTmpDirectory(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) assertEquals(expectedConfigurationFile, configurationFile) inputStreamOfDefaultConfiguration().bufferedReader().use { @@ -94,6 +94,6 @@ class IConfigurationTest : AbstractTest() { } private fun inputStreamOfDefaultConfiguration() = - IConfiguration::class.java.classLoader.getResourceAsStream(IConfiguration.DEFAULT_CONFIG_FILE_NAME) + IConfiguration::class.java.classLoader.getResourceAsStream(IConfigurationReader.DEFAULT_CONFIG_FILE_NAME) ?: error("Unable to find default configuration file in server resources") } \ No newline at end of file diff --git a/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt b/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt index ed02ccce..03fb1007 100644 --- a/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/extension/EntityExtTest.kt @@ -74,7 +74,6 @@ class EntityExtTest { val ex = assertThrows { player.sync { throw Exception("Test") - Unit } } @@ -149,9 +148,8 @@ class EntityExtTest { every { player.acquirable } returns acquirable val scope = CoroutineScope(Dispatchers.Default) - val deferred = player.async(scope) { + val deferred = player.async(scope) { throw Exception("Test") - Unit } val ex = try { From 432237fa0e920611682e43d034b98023396162d3 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 14:37:43 +0100 Subject: [PATCH 3/7] tests: Change class name --- .../{IConfigurationTest.kt => IConfigurationReaderTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/kotlin/com/github/rushyverse/api/configuration/{IConfigurationTest.kt => IConfigurationReaderTest.kt} (98%) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt similarity index 98% rename from src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt rename to src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt index 0bee5cfc..063ea48a 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/IConfigurationReaderTest.kt @@ -16,7 +16,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -class IConfigurationTest : AbstractTest() { +class IConfigurationReaderTest : AbstractTest() { @Test fun `name of default configuration file is correct`() = runTest { From 986cbab210b5283720930021d013bf8e49bbdd4f Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 15:10:32 +0100 Subject: [PATCH 4/7] fix: Use same class to find resource --- .../github/rushyverse/api/configuration/IConfigurationReader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt index b267bc45..68d4aedc 100644 --- a/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt +++ b/src/main/kotlin/com/github/rushyverse/api/configuration/IConfigurationReader.kt @@ -51,7 +51,7 @@ public interface IConfigurationReader { } val defaultConfiguration = - IConfiguration::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME) + IConfigurationReader::class.java.classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE_NAME) ?: error("Unable to find default configuration file in server resources") defaultConfiguration.use { inputStream -> From 6bf3c6909cb04c1cceecf2595f1d1d529e247eff Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 15:26:58 +0100 Subject: [PATCH 5/7] tests: Add tests for BungeeCordConfiguration --- .../BungeeCordConfigurationTest.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt new file mode 100644 index 00000000..c606b91a --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/BungeeCordConfigurationTest.kt @@ -0,0 +1,55 @@ +package com.github.rushyverse.api.configuration + +import com.github.rushyverse.api.utils.randomString +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Nested +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.assertEquals + +class BungeeCordConfigurationTest { + + @Nested + inner class Serialize { + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with empty secrets`(enabled: Boolean) { + val configuration = BungeeCordConfiguration(enabled, emptySet()) + val serialize = Json.encodeToString(BungeeCordConfiguration.serializer(), configuration) + assertEquals("{\"enabled\":$enabled,\"secrets\":[]}", serialize) + } + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with non empty secrets`(enabled: Boolean) { + val secrets = listOf(randomString(), randomString()) + val configuration = BungeeCordConfiguration(enabled, secrets.toSet()) + val serialize = Json.encodeToString(BungeeCordConfiguration.serializer(), configuration) + assertEquals("{\"enabled\":$enabled,\"secrets\":[\"${secrets[0]}\",\"${secrets[1]}\"]}", serialize) + } + + } + + @Nested + inner class Deserialize { + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with empty secrets`(enabled: Boolean) { + val string = "{\"enabled\":$enabled,\"secrets\":[]}" + val deserialize = Json.decodeFromString(BungeeCordConfiguration.serializer(), string) + assertEquals(BungeeCordConfiguration(enabled, emptySet()), deserialize) + } + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with non empty secrets`(enabled: Boolean) { + val secrets = listOf(randomString(), randomString()) + val string = "{\"enabled\":$enabled,\"secrets\":[\"${secrets[0]}\",\"${secrets[1]}\"]}" + val deserialize = Json.decodeFromString(BungeeCordConfiguration.serializer(), string) + assertEquals(BungeeCordConfiguration(enabled, secrets.toSet()), deserialize) + } + + } +} \ No newline at end of file From fd02a8b221bfcb5fa37fe0b6c89682726e0cbdc8 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 16:26:19 +0100 Subject: [PATCH 6/7] tests: Add tests for VelocityConfiguration --- .../VelocityConfigurationConfigurationTest.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt new file mode 100644 index 00000000..2ac71958 --- /dev/null +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt @@ -0,0 +1,55 @@ +package com.github.rushyverse.api.configuration + +import com.github.rushyverse.api.utils.randomString +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Nested +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource +import kotlin.test.assertEquals + +class VelocityConfigurationConfigurationTest { + + @Nested + inner class Serialize { + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with empty secret`(enabled: Boolean) { + val configuration = VelocityConfiguration(enabled, "") + val serialize = Json.encodeToString(VelocityConfiguration.serializer(), configuration) + assertEquals("{\"enabled\":$enabled,\"secret\":\"\"}", serialize) + } + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with non empty secret`(enabled: Boolean) { + val secret = randomString() + val configuration = VelocityConfiguration(enabled, secret) + val serialize = Json.encodeToString(VelocityConfiguration.serializer(), configuration) + assertEquals("{\"enabled\":$enabled,\"secret\":\"$secret\"}", serialize) + } + + } + + @Nested + inner class Deserialize { + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with empty secret`(enabled: Boolean) { + val string = "{\"enabled\":$enabled,\"secret\":\"\"}" + val deserialize = Json.decodeFromString(VelocityConfiguration.serializer(), string) + assertEquals(VelocityConfiguration(enabled, ""), deserialize) + } + + @ValueSource(booleans = [true, false]) + @ParameterizedTest + fun `with non empty secret`(enabled: Boolean) { + val secret = randomString() + val string = "{\"enabled\":$enabled,\"secret\":\"$secret\"}" + val deserialize = Json.decodeFromString(VelocityConfiguration.serializer(), string) + assertEquals(VelocityConfiguration(enabled, secret), deserialize) + } + + } +} \ No newline at end of file From 7414a912abcf6ab6b66ff09d2d2ee4555fd591d7 Mon Sep 17 00:00:00 2001 From: Distractic Date: Sun, 26 Feb 2023 17:22:54 +0100 Subject: [PATCH 7/7] tests: Rename class --- ...urationConfigurationTest.kt => VelocityConfigurationTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/test/kotlin/com/github/rushyverse/api/configuration/{VelocityConfigurationConfigurationTest.kt => VelocityConfigurationTest.kt} (97%) diff --git a/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt b/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt similarity index 97% rename from src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt rename to src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt index 2ac71958..60c1092c 100644 --- a/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationConfigurationTest.kt +++ b/src/test/kotlin/com/github/rushyverse/api/configuration/VelocityConfigurationTest.kt @@ -7,7 +7,7 @@ import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.ValueSource import kotlin.test.assertEquals -class VelocityConfigurationConfigurationTest { +class VelocityConfigurationTest { @Nested inner class Serialize {