sksamuel / hoplite

A boilerplate-free Kotlin config library for loading configuration files as data classes
Apache License 2.0
923 stars 74 forks source link

Hoplite HOCON detects wrong types #385

Closed cdprete closed 1 year ago

cdprete commented 1 year ago

Hi. I'm using the latest version of the library and I'm facing the following errors when loading a config file from a RHEL machine with Azul JDK 17:

Exception in thread "main" com.sksamuel.hoplite.ConfigException: Error loading config because:

    - Could not instantiate 'foo.Config' because:

        - 'storage': Could not instantiate class foo.StorageConfig from args [java.util.ArrayList, sun.nio.fs.UnixPath]: Expected args are [class kotlin.collections.List, class java.nio.file.Path]. Underlying error was java.nio.file.NoSuchFileException: log/faulty-messages.txt

        - 'schedule': Could not instantiate class foo.ScheduleConfig from args [sun.nio.fs.UnixPath]: Expected args are [class java.nio.file.Path]. Underlying error was java.lang.IllegalArgumentException: The schedule file config/config.csv must be readable
        at com.sksamuel.hoplite.ConfigLoader$returnOrThrow$1.invoke(ConfigLoader.kt:219)
        at com.sksamuel.hoplite.ConfigLoader$returnOrThrow$1.invoke(ConfigLoader.kt:216)
        at com.sksamuel.hoplite.fp.ValidatedKt.getOrElse(Validated.kt:115)
        at com.sksamuel.hoplite.ConfigLoader.returnOrThrow(ConfigLoader.kt:216)

The data classes are defined as such:

/**
 * @author tk35c
 * @since 13/06/2023
 **/
data class Config(val storage: StorageConfig, val source: InputConfig, val target: OutputConfig, val schedule: ScheduleConfig) {
    companion object {
        @JvmStatic
        fun fromSystemProperty(property: String): Config {
            require(property.isNotBlank()) { "The system property name can't be blank" }

            val configFilePath = System.getProperty(property).also { require(!it.isNullOrBlank()) { "Can't load the configuration without the '$property' system property set" } }
            return fromFileSystem(Path.of(configFilePath))
        }

        @JvmStatic
        fun fromFileSystem(path: Path): Config {
            require(path.isReadable() && !path.isDirectory()) { "The path must point to a readable regular file" }

            return ConfigLoaderBuilder
                .default()
                .addPathSource(path)
                .build()
                .loadConfigOrThrow()
        }
    }
}

data class InputConfig(val port: Port, val timeout: Timeout, private val hosts: Set<Host>) {
    val addresses: Set<SocketAddress>
    init {
        require(hosts.isNotEmpty()) { "At least one host needs to be specified" }

        addresses = hosts.mapTo(HashSet()) { SocketAddress(it.value, port) }
    }
}

data class OutputConfig(val port: Port, val timeout: Timeout, val nodeId: NodeId)

data class StorageConfig(@ConfigAlias("paths") private val pathList: List<StoragePath>, val faultyMessagesFile: Path) {
    val partitions = STORAGE_PARTITIONS
    val paths: List<StoragePath>

    init {
        require(pathList.isNotEmpty()) { "At least one storage path must be specified" }
        if(faultyMessagesFile.exists()) {
            require(!faultyMessagesFile.isDirectory() && faultyMessagesFile.isWritable()) { "The faulty messages file must be a writable file" }
        } else {
            val parent = faultyMessagesFile.parent
            if(parent != null) require(parent.isDirectory() && parent.isWritable()) { "The parent of $faultyMessagesFile must be a writable directory" }
            faultyMessagesFile.createFile()
        }

        paths = pathList.distinct()
    }

    private companion object {
        private const val STORAGE_PARTITIONS: Int = 32
    }
}

data class ScheduleConfig(val file: Path) {
    init {
        require(!file.isDirectory() && file.isReadable()) { "The schedule file $file must be readable" }
    }
}

The other unspecified types are just value classes. Moreover, the library enforces that a Path attribute must exists which is not always wanted. In my case, we can configure a path to a file that doesn't exist and the later the app will create it.

cdprete commented 1 year ago

I've to apologize. The working directory was wrongly set on my side and therefore the relative paths within the config were not resolvable.