Giter Club home page Giter Club logo

konf's People

Contributors

daldei avatar danysk avatar frosner avatar starsep avatar uchuhimo avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

konf's Issues

Feature: Merge map keys instead of replacing whole map

It would be nice if konf offered the feature to merge map keys instead of replacing whole map from a high precedence configuration file.
Just like spring boot properties work: https://stackoverflow.com/questions/45859131/spring-boot-merging-map-of-map-property

Another example of this:

Suppose I have this two configuration files, with the second file overriding the first one:

config1.yaml

configuration:
  property_map:
    key1: value1
    key2: value2

config2.yaml

configuration:
  property_map:
    key1: value3

And my code looks like this:

fun main() {
    var config = Config { addSpec(ConfigurationSpec) }.disable(FAIL_ON_UNKNOWN_PATH)
        .from.yaml.resource("config1.yaml")
        .from.yaml.resource("config2.yaml")

    val propertyMap = config.getOrNull<Map<String, String>>("configuration.property_map")

    println(propertyMap?.get("key1"))
    println(propertyMap?.get("key2"))
}

object ConfigurationSpec : ConfigSpec() {
    val propertyMap by optional(emptyMap<String, String>(), name = "property_map")
}

I would like to have the following output from running the above code:

value3
value2

But instead I get:

value3
null

Because it replaces the map instead of merging them.

[YAML] Path resolution failing if a key is numeric

I apologise if this is intended and documented somewhere, I couldn't find it mentioned anywhere.
Consider this yaml config file:

tree:
  1:
    myVal: true

This will fail to resolve properly (i.e. config.at("tree.1.myVal").toValue<Boolean>())).
However, if that 1 is renamed to a1, everything works properly.

RFE: optional nullable properties

I would like to have optional nullable config properties.
This is for the case where the property is a class object not a primative so simple defaults dont work well.

Example

object BootstrapConfig : ConfigSpec("bootstrap.config")
{
  data class S3Config(val url: String, val region: String ="us-east-1")
  var s3  = required<S3Config>("s3")
  val url =  optional<String>("url","")
  val file = optional<String>("file","")
}

I would like s3 to be optional as the above config is intended as a 'union type' -- only 1 of the 3 are needed. Ideally all 3 should be nullable optional.
I can create a no-arg constructor for S3Config but that is non-ideal as in other cases it may be a fairly large object or testing for 'empty' or 'exists' is non-trivial vs testing for a supplied value with empty fields.

It appears from the code that it may be nearly as simple as changing the template types to accept <Any?> instead of

That would allow either optional<S3Config?>("s3",null) or required("s3")
I did see the Item.getOrNull() but not sure how to used that correctly .. I think it was intended for the ase of a named item not existing, not for the value being empty.

Perhaps the UNSET State can be used in some way.

if( config[Config.s3].isUnset() ) null else config[Config.s3]    // verbose even if it works 

Perhaps another alternative is to make the whole thing 1 class

data class S3Config(val url: String, val region: String ="us-east-1")
data class MyConfg( val s3: S3Config? , val url: String? , val file: String?)

object BootstrapConfig : ConfigSpec("bootstrap")
{
  var config  = required<MyConfig>("config")
}

I havent tried that yet, not sure if the value prefix's will work in this nested of a config.
e.g. will "bootstrap.config.s3.url" resolve properly to env var "BOOTSTRAP_CONFIG_S3_URL"
or to property "bootstrap.config.s3.url"

Suggestions welcome

RFE: value interpolation

A highly use feature in config systems is runtime value interpolation. HCON implements this fairly crudely. A good example is log4j's configuration
This is equivalent to kotlin or groovy interpolated strings except the string to be interpolated comes from within the config values itself not in the kotlin code.
As in HCON, it is useful to be able to evaluate the values lazily so they can contain references to other values not necessarily from the same file. When using multiple formats its tricky to get right, but I belive using the property format would work well in all source formats like:

in source1.json

{ 
  "base" : { "user" : "a user" , "password" : "a password" }
}

in source2.properties

connection.jdbc=jdbc:mysql:http://${base.user}:${base.password}@server:port

Then in the app when getting 'connection.jdbc' it gets "jdbc:mysql:http://user:a password@server:port"

This can currently be done with some complex specs and lazy values or properties on a ad-hoc basis, and possibly with some kind of overloaded get() functions but its not obvious exactly how without writing a full new layer of access.

From what I can see in the code I think it would be fairly simple to implement as a direct feature, maybe with some overloading to be supplied by the user for extension.
E.g. the log4j syntax uses ${source:value} , where "source" is composed of a set of builtin and user defined configuration sources similar to konf Source's e.g. ${env:ENV_VAR} where "env" is mapped to the the environment variable source.

There might be a way of leveraging the spec prefix, config layer , loader or source names or prefixes to default much of this behaviour.

Suggestions welcome for where you think a feature of this sort 'naturally' belongs.

--
A related concept is to allow object level merging specified in the config files themselves.
HCON does this but in a very crude way (via string interpolation not structural merging).

e.g. imaging the above case but instead of string interpolation, structural merging is done

source2.properties

connection.jdbc = ${base}
connection.jdbc.server = mysql.com
connection.jsbc.user = differentuser
connection.jdbc.url = http://${user}:${password}@${server}

It may be useful to use a different syntax for structural merging then value substitution, e.g. maybe something like ${base.*} or ${@base} ... something to indicate to pull the subtree rooted at '${base}' into the current context.

It seems like this could be accomplished by creating a new layer implicitly when a structural reference is found injecting it into the current prefix. From there the normal default processing would work as if the subtree were actually inline.

Cannot load a second version of the same config file schema

I need different contexts of the same config file schema in my project. So let´s say one has a api.foo.yaml and a api.bar.yaml in which we have the same keys but with different values.

// api.foo.yaml
database:
  host: foo.com

// api.bar.yaml
database:
  host: bar.com

And I create a Config class from each file it only has the foo values, since it was loaded first.
This is because the Spec classes are singeltons (object) in Kotlin.

I don´t know if I would consider it a issue. Maybe more a feature.

Hocon substitutions

Does your Konf library support HOCON substitutions (${foo.bar})? It throws me ConfigException: com.typesafe.config.ConfigException$NotResolved: need to Config#resolve().

Please add kotlinx-bimap to published Maven release

It's impossible to use Konf out of the box, since it needs additional dependency kotlinx-bimap and shows error.
So we forced to include both artifacts to POM:

        <dependency>
            <groupId>com.github.uchuhimo</groupId>
            <artifactId>kotlinx-bimap</artifactId>
            <version>v1.1</version>
        </dependency>
        <dependency>
            <groupId>com.github.uchuhimo</groupId>
            <artifactId>konf</artifactId>
            <version>v0.11</version>
        </dependency>

It make sense to update repo with new version with full dependencies included or specify this requirement in readme.

PS: Thanks for your great work!

Nested collection of objects

Newbie to Kotlin and Konf here. Is there an easier way to achieve mapping the following yaml to the Spec below:

Or does the solution involve using the jackson mapper with config.mapper? Thank you !

datasets:
  hive:
    - key: transactions
      uri: /user/somepath
      format: parquet
      database: transations_daily
      table: transx

    - key: second_transactions
      uri: /seconduser/somepath
      format: avro
      database: transations_monthly
      table: avro_table
  file:
    - key: users
      uri: s3://filestore
      format: parquet
      mode: overwrite
object DataSetsSpec: ConfigSpec("datasets"){
    val fileDataSets by optional<List<FileDS>>(default = emptyList())
    val hive by optional<List<HiveDS>>(default = emptyList())
}

object FileDS: ConfigSpec ("file"){
    val key by required<String>(description = "Name of the dataset")
    val uri by required<String>(description = "Target path of the file with the protocol qualifier")
    val format by optional<String>("parquet", "File format", "Format in which the file must be writte")
    val mode by optional<String>("append", "Save Mode", "Mode in which the file would be written - (overwrite, append)")
}

object HiveDS: ConfigSpec ("hive"){
    val key by required<String>()
    val uri by required<String>()
    val database by required<String>()
    val table by required<String>()
    val format by optional<String>("parquet", "File format", "Format in which the file must be writte")
    val mode by optional<String>("append", "Save Mode", "Mode in which the file would be written - (overwrite, append)")
}

Case-insensitive resolving of keys

I encountered an issue with reading configuration from the environment which made me realize keys are apparently case-sensitive. This is particularly annoying when keys from the environment are all lower-case'd while my ConfigSpec uses camelCase (e.g: DATASOURCECLASSNAME does not map to dataSourceClassName)

Have you considered the ability to resolve keys case-insensitively?

Is it possible from an already built map to update all values from that map?

Say I have 3 entries in my configuration A: Int, B: String and C: Float. Now I got a map like [A = 10, B = "ciao"] and i want to set those values. One way would be to write this file as a JSON and reload from file, is it the only way? It would be perfect something like conf.loadFromMap(myMap). Or maybe myMap.forEach { k, v -> conf[k] = v }

com.uchuhimo.konf.NameConflictException

I have environment variables that I'd like to access via config specs:

export KAFKA_CLIENT_CERT=some_cert
export KAFKA_CLIENT_CERT_KEY=some_cert_key

Here's my config spec:

object KafkaConfigSpec : ConfigSpec("kafka") {
    val clientCert by optional(name="client.cert", default = "")
    val clientCertKey by optional(name="client.cert.key", default = "")
}

When I try to load the config:

Config { addSpec(KafkaConfigSpec) }.withSourceFrom.env()

I get the following stack trace:

Exception in thread "main" com.uchuhimo.konf.NameConflictException: item kafka.client.cert.key cannot be added
	at com.uchuhimo.konf.BaseConfig.addSpec(BaseConfig.kt:415)
	at com.uchuhimo.konf.Config$Companion.invoke(Config.kt:344)

Strict parsing

Great work, I was thinking of using it in my current project. One requirement I have, though, is to allow strict parsing, meaning that if a key appears in the configuration, and this key is not part of the Spec, the application should fail to start.

Ideally, there should be a function, at Config level, returning all the keys that are not part of the schema.

Integrating with android application

I am trying to integrate Konf lib with android app but facing issues while creating Config object.
java.lang.ExceptionInInitializerError I am getting this exception while creating Config object.

Inconvenient way of handling lists in FlatSource

When using something like .withSourceFrom.env() or .withSourceFrom.systemProperties() for an OptionalItem<List<String>> for example, the current implementation expects multiple environment variables defined like this: MY_KEY_0, MY_KEY_1 and so on. This is quite inconvenient.

It would be nice to parse MY_KEY either as a JSON or comma separated values.

Nested object properties

Hey @uchuhimo thanks again for your great work here. It would be great to be able to specify required and optional nested object properties.

At the moment, this is technically possible, but a bit awkward, requiring to use a Map<String, Any>, and then using that to create a Config, perhaps with one or more Specs.

Ideally, the following should work:

val outer = object : ConfigSpec() {
    
     val inner = object : ConfigSpec("address") {
            
            val host by required<String>()
            val port by required<Int>()
     }
     
     val settings by required<Config>().withSchema(inner)
}

val configuration = Config.invoke { addSpec(outer) }.from.map.kv(mapOf("settings.address.host" to "127.0.0.1", "settings.address.port" to 8080))

val settings = configuration[outer.settings]
val host = settings[outer.inner.host]

Not sure if there's a better way of doing the above, perhaps by using multiple Specs.

validateRequired enhancements

  1. config.validateRequired() does not check for empty values only nulls
  2. the throw UnsetValueException holds only item name, does not telling which exact item is not set.

For instance if you have a configuration like this:

host:
   url:
   port:

database:
   url:
   port:

UnsetValueException will for instance throw "item url not set", better would be "host.url not set"

Optional config item without default value

Is it possible to create a config property which is optional without having to provide a default value? Thinking something like the below which can be retrieved from the Config object as a Kotlin nullable variable. Have the case where a config value is either defined or not, there would be no default value.

object AppSpec : ConfigSpec() {
    val host by required("0.0.0.0")
    val resourcePath by optional<String>() // no default value
}

Then when retrieved something like

// as nullable var
val resourcePath : String? = config[AppSpec.resourcePath]

(Great library by the way)

Load configuration values from optional files or resources

Hello!

Thanks a lot for all the good work you put in that library, it looks really nice!
I tested it on simple use cases and everything is going really fine for me, but I have one use case I'd like to use it for, and I can't manage to find a solution yet.

I had a look at the documentation, the source code, and all past issues, but couldn't manage to find an answer, which is why I created this issue.

My concern is pretty simple, right now I'm writting this:

val configuration = Config { addSpec(ServerSpec) }
    .from.yaml.resource("production.yml")
    .from.yaml.resource("local.yml")
    .from.yaml.watchFile("./debug.yml")
    .from.env()
    .from.systemProperties()

Because what I'd like to have is:

  • load the configuration from the production.yml resource (which will always be there, and packaged for production)
  • if I'm on my development environment, I'll have a local.yml file as a resource as well, but it is ignored by Git, so it won't be packaged. I'd like to load it when it is present, but it'll obviously won't be there on production,
  • while on production, if a file called debug.yml is present at the path I specified, I'd like to load it and use its values, otherwise, I'd like it to be ignored.

Right now my issue is that if something isn't present, I have exceptions such as: java.io.FileNotFoundException: config.yml (No such file or directory). Which is normal since the file doesn't exist, but I'd like the framework just to ignore that source silently.

From my point of view, I could either write something like:

val configuration = Config { addSpec(ServerSpec) }
    .disable(Feature.FAIL_ON_UNFOUND_SOURCE)
    .from.yaml.resource("production.yml")
    .from.yaml.resource("local.yml")
    .from.yaml.watchFile("./debug.yml")
    .from.env()
    .from.systemProperties()

or something like:

val configuration = Config { addSpec(ServerSpec) }
    .from.optional.yaml.resource("production.yml")
    .from.optional.yaml.resource("local.yml")
    .from.optional.yaml.watchFile("./debug.yml")
    .from.env()
    .from.systemProperties()

Of course if there are any other solutions (existing or not) you have in mind, do not hesitate to let me know.

What do you think about this use case? If I can be of any help regarding the resolution of that issue, do not hesitate to tell me.

Thanks a lot for your attention and your help! Have a great day!

toml provider file discovery issue?

Hi - first time using this (it's really nice BTW), and I think I found a minor issue.

using the auto-extension discovery feature, it looks like it's limited to json and properties file types currently. e.g. If I send a .toml file to this function:

fun loadConfig(configFile: File): AppConfig {
    return Config()
        .from.file(configFile)
        .toValue()
}

it fails with com.uchuhimo.konf.source.UnsupportedExtensionException: cannot detect supported extension for "test-config.toml", supported extensions: conf, json, properties, toml, xml, yml, yaml.
It looks like this is because of DefaultLoaders:137, where it calls

 fun of(extension: String): Provider? =
            extensionToProvider[extension]

whose implementation is this:

private val extensionToProvider = ConcurrentHashMap(mutableMapOf(
            "json" to JsonProvider,
            "properties" to PropertiesProvider
        ))

my sample loadConfig works fine if I use .from.toml.file(configFile).
See anything I'm doing wrong? It's not a big deal either way. Thanks.

Konf examples

Hi,
where can I find a library of examples for konf? I'm trying to get past the base example presented, but I can't understand how to do more advanced stuff with the library.
I want to parse e.g.:

tests:
  - description: "latex guide should build"
    configuration:
      tasks: [ "buildLatex" ]
      options: [ "--debug" ]
    expectation:
      file_exists: [ "output.pdf" ]

I tried the following:

data class Configuration(val tasks: List<String>, val options: List<String>)
data class Expectation(val file_exists: List<String>)
data class Test(val description: String, val configuration: Configuration, val expectation: Expectation)
object Root : ConfigSpec("") {
    val tests by required<List<Test>>()
}
val testConfiguration = Config().from.yaml.inputStream(myInputStream)
println(testConfiguration[Root.tests])

But this causes:

java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
	at kotlin.reflect.jvm.internal.calls.CallerImpl$Constructor.call(CallerImpl.kt:41)
	at kotlin.reflect.jvm.internal.KCallableImpl.call(KCallableImpl.kt:106)
	at kotlin.reflect.jvm.internal.KCallableImpl.callDefaultMethod$kotlin_reflection(KCallableImpl.kt:152)
	at kotlin.reflect.jvm.internal.KCallableImpl.callBy(KCallableImpl.kt:110)
	at kotlin.reflect.full.KClasses.createInstance(KClasses.kt:283)
	at io.kotlintest.runner.jvm.JvmKt.instantiateSpec(jvm.kt:15)
	at io.kotlintest.runner.jvm.TestEngine.createSpec(TestEngine.kt:122)
	at io.kotlintest.runner.jvm.TestEngine.access$createSpec(TestEngine.kt:19)
	at io.kotlintest.runner.jvm.TestEngine$submitSpec$1.run(TestEngine.kt:105)
	at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: com.uchuhimo.konf.NoSuchItemException: cannot find item tests in config
	at com.uchuhimo.konf.BaseConfig.getOrNull(BaseConfig.kt:248)
	at com.uchuhimo.konf.BaseConfig.getOrNull(BaseConfig.kt:245)
	at com.uchuhimo.konf.BaseConfig.getOrNull$default(BaseConfig.kt:188)
	at com.uchuhimo.konf.BaseConfig.get(BaseConfig.kt:168)
	at my.call.site

It's unclear to me why and how can I debug this.

Thanks!

RFE: Load Config into child prefix. Or reuse ConfigSpec at new prefix

I am trying to accomplish the following:

In one source (or ideally in the same source, but for simplictiy, 1 source )

base.properties:

auth.user=username
auth.password=password

In another source I would like to reuse 'auth' as a default for other values.

service.properties

service1.url=http://myservice
service2.url=http://myservice
service2.auth.user=user1
service2.auth.password=user2

In Application


data class Auth(
   val user="",
   val password=""
)


object ServiceSpec :  ConfigSpec() { 
       val url by required<String>()
       val auth by required<Auth>()
}

I would like to instantiate something like


class Service( config: Config ) {
    val url by config.property( ServiceSpec.url )
    val auth by config.property( ServiceSpec.auth )
}

Then in 'pseudo code'


val authConfig  = Config { addSpec( ? ) }.withSourceFrom.file("base.properties")
val ServiceConfig = Config  { addSpec( ServiceSpec( ? )  } ?? 
val services = Config( ServiceConfig{ ? } ).withSourceFrom("service.properties")

val service1 = Service( ServiceConfig("service1" ).withValuesFrom(services) ?? .withDefaultsFrom( authConfig ) )
val service2 = Service( ServiceConfig("service2") .withValuesFrom(services)  ?? .withDefaultsFrom(authConfig)) 

Couple unresolved issues

  1. How to reuse a the same type of ConfigSpec at different prefix's ? ("service1" and "service1" are the same type at different prefix's
  2. How to inject defaults from a unrelated config -- into different prefix's

For while I thought the FlatSource( prefix="xxx" ) might work, but it does not. The prefix's are all absolute. Unless I am missing something you cannot derive specs from other specs or load sources or values into any but the root prefix.

This is a very similar but orthagonal feature that layers provide. Layers let you have defaults on the same prefix but different named layers.

Is there currently a way, or is it conceviable to implement something like
Config.loadSourceAt("prefix.nested").from.file("file")
and/or
ConfigSpec( parent: ConfigSpec , atPrefix: String )

Essentially be able to define ConfigSpecs as reusable types that may occur at different places in a config hierarchy, AND be able to load Sources from the same or different locations into different prefix'ed roots.

Config{}.toYaml.toFile(file) doesn't close OutputStream

I am using this library to create temporary configuration files in folders that may be removed by the program or another process later. If I update config and export it to file with .toYaml.toFile(file), the file becomes locked, cause the library doesn't close the OutputStream after the write.

Try running this test (I'm using Apache Commons to delete the file)

internal class Test {

    private object TestSpec : ConfigSpec() {
        val testVal by optional("test")
    }

    @Test
    fun testKonfFileAccess() {
        val file = File.createTempFile("test", ".yaml")

        Config { addSpec(TestSpec) }
            .toYaml.toFile(file)

        FileUtils.forceDelete(file)
    }
}

You would get an IOException.

As a workaround you can write the file contents with kotlin's writeText:

file.writeText(
    Config { addSpec(TestSpec) }
        .toYaml.toText()
)

But I belive that's this is not an intended behavior.

TOML Table array mapping

Hey, I've been trying to map toml table array but it returns an empty list.

TOML:

[[editors]]
id = "bytespy.jvm.bytecode.editor"
impl = "dev.cubxity.bytespy.jvm.impl.editor.BytecodeContentEditor"
content = "org.objectweb.asm.tree.ClassNode"
description = "JVM Bytecode editor"

[[editors]]
id = "bytespy.jvm.bytecode.decompiler"
impl = "dev.cubxity.bytespy.jvm.impl.editor.DecompiledContentEditor"
content = "org.objectweb.asm.tree.ClassNode"
description = "JVM Bytecode decompiler"

ConfigSpec:

object ManifestSpec : ConfigSpec() {
    val editors by optional(listOf<Editor>())
}

Editor:

data class Editor (
     val id: String,
     val impl: String,
     val content: String,
     val description: String
)

Any help would be appreciated :)

combining configs is not working

    val configDefault = Config {addSpec(ApplicationSpec)   }
      .from.yaml.resource("default.yml")


    val configEnv = Config{ addSpec(ApplicationSpec) }
      .from.env() // SERVER_HOST=0.0.0.0
      .from.systemProperties()  // -Dserver.host=0.0.0.0 -Dserver.port=8080


val totalConfig = configDefault + configLoad 

totalConfig.contains("value) // this will show true
totaConfig<String>("value) // this will fail

Modularize project to minimize unnecessary transitive dependencies

I currently think the amount of supported formats and dependencies is excessive for most projects. I think we should move the JSON, XML, YAML, HOCON, and TOML providers into separate modules, and let users opt-in to use these different providers. Furthermore I also think it would be a good idea to move the functionality for loading from git repositories into a different module. The primary motivation for this is to reduced amount of dependencies on the core library.

What I propose is to have 7 different modules in total;

  • konf-core
  • konf-json
  • konf-xml
  • konf-yaml
  • konf-hocon
  • konf-toml
  • konf-jgit

I've forked the project and started making progress on this in a separate branch. I intend the API to stay mostly unchanged, however it's necessary to remove some fields and replace them with extension properties (DefaultLoaders specifically). The README and documentation must be updated to reflect these few changes, and add that you must manually add providers as a dependency, and register them to the Provider companion object.

Required fields not required?

I expect this to throw an exception when executed because I don't have either value defined in my Environment:

object DatabaseSpec : ConfigSpec(){
    val connectionString by required<String>()
    val driver by required<String>()
}

val config =    Config{
                    addSpec(DatabaseSpec)
                }
                    .from.env()

But it does not throw an exception. Am I misunderstanding what "required" means?

Cannot cast a spec to data class (in general issue in casting)

1. To reproduce the error run the following code.:
Main.kt

val config = Config { addSpec(ConfigurationSpec) }.enable(Feature.OPTIONAL_SOURCE_BY_DEFAULT)
	.from.json.file("config.json")
	.from.properties.file("config.prop")
	.from.properties.file("config.properties")
	.from.hocon.file("config.conf")
	.validateRequired()
	.toValue<Configuration>()

ConfigurationSpec.kt:

package io.github.xyz.xyz

import com.uchuhimo.konf.*
import io.github.animeshz.air_bent.*
import java.io.*
import java.nio.file.*

object ConfigurationSpec : ConfigSpec("configuration") {
	object Server : ConfigSpec("server") {
		val port by optional(80)
		val dbFile by optional("${Paths.get("").toAbsolutePath().toString().trimEnd(File.separatorChar) + File.separator}data.db", "database file")
	}

	object Hardware : ConfigSpec("hardware") {
		val A0 by required<Configuration.SerialAddressConfiguration>()
		val A1 by required<Configuration.SerialAddressConfiguration>()

		val rShunt by required<Double>("resistor shunt")
		val maxExpectedCurrent by optional(10.0, "max expected current")
	}
}

Confguration.kt:

package io.github.xyz.xyz

data class Configuration (val server: Server, val hardware: Hardware){
	enum class SerialAddressConfiguration(val value: Int) {
		GND(0),
		VS(1),
		SDA(2),
		SCL(3)
	}

	data class Server(val port: Int, val dbFile: String)
	data class Hardware(val A0: SerialAddressConfiguration, val A1: SerialAddressConfiguration, val rShunt: Double, val maxExpectedCurrent: Double)
}

Sample hocon file:

configuration = {
  server = {
    port = 80
    database file = "data.db"
  }

  hardware = {
    resistor shunt = 0.005
    max expected current = 10.0

    A0 = "GND"
    A1 = "GND"
  }
}

2. Error occurred:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: com.uchuhimo.konf.NameConflictException: item root cannot be added
	at com.uchuhimo.konf.BaseConfig.addItem(BaseConfig.kt:530)
	at com.uchuhimo.konf.RequiredConfigProperty.provideDelegate(Config.kt:399)
	at io.github.xyz.xyz.MainKt.<clinit>(Main.kt:53)

3. Expected behavior:
Should cast the spec to the Configuration data class. I did not cast to data class directly because configuration hocon file has spaces in between the keys which cannot be casted to standard kotlin as variables with spaces donot exist :(

Top-level lists

Does Konf support top-level lists in, for example, YAML files?

- 
	key: value
	otherkey: value

-
	key: value
	otherkey: value

I couldn't find any documentation on this specific use-case - in my case, I can't modify the file to add a root key. Any chance of support for this?

LoadException

Exception in thread "main" com.uchuhimo.konf.source.LoadException: fail to load web.host
	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:748)
	at com.uchuhimo.konf.source.SourceKt$load$$inlined$apply$lambda$1.invoke(Source.kt:756)
	at com.uchuhimo.konf.source.SourceKt$load$$inlined$apply$lambda$1.invoke(Source.kt)
	at com.uchuhimo.konf.BaseConfig.lock(BaseConfig.kt:62)
	at com.uchuhimo.konf.source.SourceKt.load(Source.kt:754)
	at com.uchuhimo.konf.BaseConfig.addSource(BaseConfig.kt:464)
	at com.uchuhimo.konf.Config$DefaultImpls.withSource(Config.kt:249)
	at com.uchuhimo.konf.BaseConfig.withSource(BaseConfig.kt:47)
	at com.uchuhimo.konf.source.Loader.resource(Loader.kt:264)
	at com.xxxx.data.server.ConfigKt.classpath(Config.kt:36)
	at com.xxxx.data.server.Configuration.load(Config.kt:57)
	at com.xxxx.data.server.ApplicationKt.main(Application.kt:7)
Caused by: com.uchuhimo.konf.source.NoSuchPathException: cannot find path "host" in source [type: value, url: file:/home/icode/projects/data-center/server/build/resources/main/config.yml, resource: config.yml]
	at com.uchuhimo.konf.source.base.ValueSource.getOrNull(ValueSource.kt:61)
	at com.uchuhimo.konf.source.base.MapSource.getOrNull(MapSource.kt:50)
	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:734)
	... 11 more

please ignore not exist file, and ignore config file not exist key

Spec#get produces unexpected results

When you try to retrieve a "leaf spec", its innerSpecs is not empty. This behavior is unexpected because the spec doesn't have any nested specs declared, and therefore it shouldn't have any innerSpecs.

You can run the code below to reproduce the issue:

fun main() {
    check(Service.Backend.Login.innerSpecs.isEmpty()) // innerSpecs is empty as expected
    check(Service["service.backend.login"].innerSpecs.isEmpty())
        { "innerSpecs should be empty because the Login spec doesn't have any nested specs" }
}

// From the example in the README file
object Service : ConfigSpec() {
    object Backend : ConfigSpec() {
        object Login : ConfigSpec() { // Our "leaf spec"
            val user by optional("admin")
        }
    }
}

Futhermore looking at the nested tests part of ConfigSpecTestSpec, a lot of the results seems unexpected to me, so maybe I'm just not quite understanding the API.

Example:

assertThat(spec["a.bb.inner"].items, isEmpty)

This test passes, despite the Nested.Inner object having an item named item declared.

Example is misleading

In com.uchuhimo.konf.example.Config.kt the example is confusing/misleading:

This code:

    val basePort by ConfigSpec("server").required<Int>()
    config.lazySet(Server.port) { it[basePort] + 1 }
    config.lazySet("server.port") { it[basePort] + 1 }
    run {
        var port by config.property(Server.port)
        port = 9090
        check(port == 9090)
    }

Is not clear what the intent is. I doesnt use 'basePort' at all.

Here is an example that may be what the intent was


    val baseSpec =  ConfigSpec("server")
    val basePort by baseSpec.required<Int>()
    config.addSpec( baseSpec )
    config.lazySet(Server.port) { it[basePort] + 1 }
    config.lazySet("server.port") { it[basePort] + 1 }
    run {
        config[basePort] = 8000
        val port by config.property(Server.port)
        check( port == 8001 )
    }

Consider removing Property classes from Spec

This definition is still valid even though it can't be used anymore:

object Server : ConfigSpec("server") {
    val url = required<String>("url")
}

It seems these are no longer used anywhere so it is safe to remove the code.

How to write new values to config file?

Is there any way to save the changed values to the same file from which the values were loaded, so when the program is run a second time then the new values are loaded?

Extend LOAD_KEYS_CASE_INSENSITIVELY to support something like snake_case

Please think about following example code.

object ExampleSpec : ConfigSpec("") {
    private val exampleKey by required<String>()
    
    fun loadExampleKey(): String {
        val config = Config { addSpec(ExampleSpec) }
        	.enable(Feature.LOAD_KEYS_CASE_INSENSITIVELY)
            .withSource(
            	Source.from.json.file(File("config.json"))
            )
		
        return config[exampleKey]
    }
}

With the code above, We can load:

{
  "exampleKey": "value"
}

But we cannot load with error:
Exception in thread "main" java.util.concurrent.ExecutionException: com.uchuhimo.konf.UnsetValueException: item indentStyle is unset

  "example_key": "value"

It's very nice if both CamelCase and snake_case are supported, especially when we load config from multiple file type such as json and yaml.

env override new user question

I'm having trouble figuring out the correct syntax/configuration for from.env() support.
I created a repo with some tests showing the TOML config file I'd like to use.
https://github.com/dtanner/konf-question

Can you help me understand what I need to do to make the test config from toml and env should produce y for optionA name pass? It's currently failing with this error:

com.uchuhimo.konf.source.LoadException: fail to load root

	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:530)
	at com.uchuhimo.konf.BaseConfig.addItem(BaseConfig.kt:542)
	at com.uchuhimo.konf.RequiredConfigProperty.provideDelegate(Config.kt:399)
	at question.AppKt.loadConfigFromTomlAndEnv(App.kt:45)
	at question.AppTest.config from toml and env should produce y for optionA name(AppTest.kt:29)
	....
        com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: com.uchuhimo.konf.source.ObjectMappingException: unable to map source {appconfig={outputs={optiona={name=y}}}} in [type: system-environment] to value of type AppConfig
	at com.uchuhimo.konf.source.SourceKt.toValue(Source.kt:834)
	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:523)
	... 55 more
Caused by: com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException: Instantiation of [simple type, class question.AppConfig] value failed for JSON property outputs due to missing (therefore NULL) value for creator parameter outputs which is a non-nullable type
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: question.AppConfig["outputs"])

Feature proposal: value validators

It would be nice to have value validation implemented. My idea: required, optional should have two more lambda arguments, which are both optional:

  • add validIf parameter to etc. with following definition: (value : T) -> Boolean
  • add invalidValueLazyMessage with definition: (value : T) -> String?

Usage example:

object ExampleConfig : ConfigSpec() {
    val foo by required<Int>(validIf = { it in (0..42) }, invalidValueLazyMessage = { "foo should be between 0 and 42, but is $it" })
}

If validIf returns false, konf throws an exception with optional message evaluated from invalidValueLazyMessage.

Empty YAML config file causes IllegalArgumentException when loading

I was trying to use konf to manage my config files and migrations, together with default config.

It looks like konf can't handle, when file:

  • doesn't exist
  • exists, but is empty

The stacktrace for empty YAML file:

Exception in thread "main" java.lang.IllegalArgumentException: Parameter specified as non-null is null: method com.uchuhimo.konf.source.yaml.YamlConstructor$1.construct, parameter node
	at com.uchuhimo.konf.source.yaml.YamlConstructor$1.construct(YamlProvider.kt)
	at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:151)
	at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:525)
	at org.yaml.snakeyaml.Yaml.load(Yaml.java:453)
	at com.uchuhimo.konf.source.yaml.YamlProvider.inputStream(YamlProvider.kt:45)
	at com.uchuhimo.konf.source.Provider$DefaultImpls.file(Provider.kt:84)
	at com.uchuhimo.konf.source.yaml.YamlProvider.file(YamlProvider.kt:37)
	at com.uchuhimo.konf.source.Loader.file(Loader.kt:81)
	at com.uchuhimo.konf.source.Loader.file$default(Loader.kt:80)
	at dev.bloodstone.konf_test.MainKt.main(Main.kt:24)

Adding dummy-value: to config, causes it to load.

I don't know how important is handling of such edge-case, since it looks like konf is not made for my use case, and such things shouldn't happen under normal circuimstances. But I thought I'd report it anyway.

Absence of defined variables overrides defined variables?

Hi - trying out Konf and seeing this behaviour:

object ServicingConfig : ConfigSpec("servicing") {
    val baseURL by required<String>("baseURL")
}
val config = Config {
     addSpec(ServicingConfig)
}.withSource(
     Source.from.hocon.resource("application.conf") +
        Source.from.env()
)

with application.conf:

servicing {
  baseURL = "https://service/api"
}

the result is that config is empty. However, if I remove Source.from.env() it works as expected. My expectation was that I can override variables set in the hocon config with environmental variables (on a per environment basis for example), the absence of an environmental variable won't displace the defined variable in HOCON. Is that an unrealistic expectation?

com.uchuhimo.* dependencies not found from version 0.15 and above

Somehow from version 0.15.x and above, in my gradle project somehow it fails to compile due to missing dependencies of com.uchuhimo.*, e.g:
{code}

Task :compileKotlin FAILED
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (5, 12): Unresolved reference: uchuhimo
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (6, 12): Unresolved reference: uchuhimo
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (7, 12): Unresolved reference: uchuhimo
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (11, 26): Unresolved reference: Config
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (13, 24): Unresolved reference: ConfigSpec
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (28, 33): Unresolved reference: optional
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (29, 41): Unresolved reference: required
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (30, 37): Unresolved reference: optional
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (31, 39): Unresolved reference: required
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (32, 36): Unresolved reference: optional
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (49, 30): Unresolved reference: Config
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (50, 21): Unresolved reference: addSpec
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (53, 26): Unresolved reference: ConfigException
e: /Users/oalsafi/Private/Projects/kafka-consumer-lag-monitoring/src/main/kotlin/com/omarsmak/kafka/consumer/lag/monitoring/config/KafkaConsumerLagClientConfig.kt: (65, 22): Unresolved reference: ConfigException

FAILURE: Build failed with an exception.

  • What went wrong:
    Execution failed for task ':compileKotlin'.

Compilation error. See log for more details

  • Try:
    {code}
    This only happens if I update the version to anything from 0.15.x, 0.14.1 works fine. Any hints?

Thanks

RFE: Bootstrap Loader

I would like to implement a lighter/better/simpler version of Spring cloud-config.
The concept is you have a 'bootstrap' loader which configures the source of further configurations.
This bootstrap load then could specify a URL or other source in which to read the rest of the configurations.

This can be done currently in 2 steps, first create a Config for the bootstrap which would have the a configuration specifying the Source/Loader to load. Then parse that and create a new Config using your ConfigSpec and add the .withSourceFrom.xxxx statements from the bootstrap process.
This was the rational for the previous pull request, so a URL could include the format making the bootstrap able to use a config file in any format easily.

Once a bootstrap is working then I am working on mini-server that will server config requests ... that has a bit more complexity in it as it needs to server to many clients and to pull out of the config files sub-trees of configurations. Not sure how I plan on doing that yet as the ConfigSpec would not be in the server side only the loader, but its useful for it to be able to parse the files fully so it can pull out subtrees ... which requires a kind of ConfigSpec for that (or a bunch more low level code).

Ultimately Im aiming at kotlin-script based DSL that could be dynamically loaded.

But first step, a consistent config format to specify config sources and/or loaders.
Any suggestions welcome.

I have also implemented a basic s3 Loader essentially this:

fun getS3ObjectAsStream( s3url: String , region: String ) = ... 

...

fun Loader.s3(s3url: String , region: String): Config =
    config.withSource(provider.fromInputStream(getS3ObjectAsStream(s3url,region=region)))

...

val configSpec =  Config { addSpec(MyConfig) }
    .withSourceFrom.properties.s3("s3://mys3bucket/test/config.properties" , region="us-east-1" )

The goal being to put protected config files in S3 and then use the running app's authentication to load from them. Ultimately the above would be configured in the bootstrap, possibly augmented with system properties. In this case Im using it from within a AWS Lambda function which has limited access to the environment so it must get everything from outside or from env variables.

I am working on enhancing the URL provider to accept the s3 scheme, doing it the 'normal' way of adding new protocols to Java doesn't work in Lambda due to the security manager so it need be done with custom code. (aka the getS3ObjectAsStream())

Also, thank you ! I have been looking for finding or writing a good config system and have had many dead-ends that were just not quite right -- (including my own) -- and finally found konf !! This is by far the cleanest and most generally useful config library Ive found yet so am basing my future apps on it.

Unable to interpolate Integers?

given

database {
  name = "servicedb"
  hostname = "db"
  port = 5432
}
flyway {
  url= "jdbc:postgresql://${database.hostname}:${database.port:-5432}/${database.name}"
}

it fails with

Caused by: com.uchuhimo.konf.source.WrongTypeException: source 5432 in [type: HOCON, url: file:/xxx/server/build/resources/main/application.conf, resource: application.conf] has type Integer rather than String
	at com.uchuhimo.konf.source.SourceKt.asValueOf(Source.kt:448) ~[konf-core-0.22.1.jar:na]
	at com.uchuhimo.konf.source.TreeLookup.lookup(Source.kt:407) ~[konf-core-0.22.1.jar:na]
	at org.apache.commons.text.lookup.InterpolatorStringLookup.lookup(InterpolatorStringLookup.java:151) ~[commons-text-1.8.jar:1.8]

is it possible to do this?

Default Configuration creation

Hi,

is there any way for creating a default configuration file. For example upon startup. I know there are functions like config.toJson('config.json'), but those functions only output the current values within the config object.

Imagine this situation:

import com.uchuhimo.konf.Config
import com.uchuhimo.konf.ConfigSpec

object ServerSpec : ConfigSpec() {
    val host by optional("0.0.0.0")
    val port by required<Int>()
}

object ClientSpec : ConfigSpec() {
    val test by required<String>(description = "some test value")
}

val config = Config()
config.addSpec(ServerSpec)
config.addSpec(ClientSpec)
config.toJson.toFile("config.json")

The output to config.json will be

{
  "server" : {
    "host" : "0.0.0.0"
  }
}

instead of a fully valid config.json file with all required parameters.
It would make a devs life much easier, when the generated config file would contain the required parameters as well, because then you can simply check upon startup if the config file exists / contains all required fields, if not it outputs the standard values with a hint to update those.

layer and yaml don't work together as expected

I'm finding that toValue fails with a layered configs.

Test code:

package st.legevent

import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.uchuhimo.konf.BaseConfig
import com.uchuhimo.konf.Config
import com.uchuhimo.konf.source.LoadException
import com.uchuhimo.konf.source.yaml
import com.uchuhimo.konf.toValue
import org.junit.Test

class TestConfigTestReport {

    @Test
    fun succeed1() {
        val config = Config()
            .from.yaml.resource("testKonf.yml")

        loadAndPrint(config)
    }

    @Test
    fun succeed2() {
        val config = BaseConfig(
            mapper = ObjectMapper(/*YAMLFactory()*/)
                .registerKotlinModule()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        )
            .from.yaml.resource("testKonf.yml")

        loadAndPrint(config)
    }

    @Test(expected = LoadException::class)
    fun fail1() {
        val config = Config()
            .from.yaml.resource("testKonf.yml")
            .from.yaml.file(
                System.getenv("SERVICE_CONFIG") ?: "/opt/legacy-event-service/conf/legacy-event-service.yml",
                true
            )

        loadAndPrint(config)
    }

    @Test(expected = LoadException::class)
    fun fail2() {
        val config = BaseConfig(
            mapper = ObjectMapper(/*YAMLFactory()*/)
                .registerKotlinModule()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        )
            .from.yaml.resource("testKonf.yml")
            .from.yaml.file(
                System.getenv("SERVICE_CONFIG") ?: "/opt/legacy-event-service/conf/legacy-event-service.yml",
                true
            )

        loadAndPrint(config)
    }

    @Test(expected = LoadException::class)
    fun fail3() {
        val config = BaseConfig(
            mapper = ObjectMapper(/*YAMLFactory()*/)
                .registerKotlinModule()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        )
            .from.yaml.resource("testKonf.yml")
            .from.env()

        loadAndPrint(config)
    }

    @Test(expected = LoadException::class)
    fun fail4() {
        val config = Config()
            .from.yaml.resource("testKonf.yml")
            .from.env()

        loadAndPrint(config)
    }

    @Test(expected = LoadException::class)
    fun fail5() {
        val config = BaseConfig(
            mapper = ObjectMapper(/*YAMLFactory()*/)
                .registerKotlinModule()
                .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
        )
            .from.yaml.resource("testKonf.yml")
            .from.systemProperties()

        loadAndPrint(config)
    }

    @Test(expected = LoadException::class)
    fun fail6() {
        val config = Config()
            .from.yaml.resource("testKonf.yml")
            .from.systemProperties()

        loadAndPrint(config)
    }

}

private fun loadAndPrint(config: Config) {
    val db = config.toValue<ConfigTestReport>()

    println(db)
}

data class ConfigTestReport(val db: Map<String, *>)

Contents of src/test/resources/testKonf.yml:

db:
  driverClassName: org.h2.Driver
  url: 'jdbc:h2:mem:db;DB_CLOSE_DELAY=-1'

Support for non map based StdSerializer

Given the following custom StdSerializer, it's not possible to serialize my custom object as a String without getting errors. An json object is expected because it needs to be able to convert to a Map. So far it doesn't seem to affect deserialization.

The following example isn't supported: (example error: https://paste.ubuntu.com/p/MhTV9333Qr/)

internal class TextSerializer : StdSerializer<Text>(Text::class.java) {

  override fun serialize(value: Text, gen: JsonGenerator, provider: SerializerProvider) {
    gen.writeString(value.toPlain())
  }
}

This works:

internal class TextSerializer : StdSerializer<Text>(Text::class.java) {

  override fun serialize(value: Text, gen: JsonGenerator, provider: SerializerProvider) {
    gen.writeStartObject()
    gen.writeStringField("text", value.toPlain())
    gen.writeEndObject()
  }
}

[question] Map config item on multiple types

Sorry for a question in the issues but I don't find a place where I can ask it.

Example toml config:

[storage.local_s3]
type = "s3"
host = "http://localhost:8000"
access_key = "123456789"
secret_key = "abcdef"

[storage.local_fs]
type = "fs"
path = "/var/lib/storage"
sealed class Storage {
    class FS(val path: String) : Storage
    class S3(val host: String, val accessKey: String, val secretKey: String) : Storage
}

Is it possible that storages config item would contain a map with correspondingStorage objects?

Maybe there is an extension point in the library to do that trick but I cannot find it. So what I want is something like this:

fun storageTypeMapper(source: Source): Storage {
    val type = source.get("type").toText()
    return when(type) {
        "fs" -> Storage.FS(...)
        "s3" -> Storage.S3(...)
        else -> throw SourceException("...")
    }
}

class MyAppSpec : ConfigSpec() {
    val storages by required<Map<String, Storage>>("storage", typeMapper = storageTypeMapper))
}

No support for having no prefix?

Hello,

Say I have the following set of existing environment variables within my infrastructure:

DATABASE=foo.example.com
DATABASE_USER=john
DATABASE_PASS=doe

I want to, without modifying them, be able to resolve these variables from both YAML file and environment variables, with latter having precedence.

It seems that such naming is discouraged by konf.

I tried the following:

object Root : ConfigSpec("") {
    val DATABASE by required<String>()
    val DATABASE_USER by required<String>()
    val DATABASE_PASS by required<String>()
}

Config {
    addSpec(Root)
}
        .from.yaml.resource("server.yml")
        .from.env()
        .from.systemProperties()

With YAML file having similar structure to the above:

DATABASE: foo.example.com
DATABASE_USER: john
DATABASE_PASS: doe

Sadly, the moment DATABASE_PASS or similar variable appears in the env, I get the following exception:

Exception in thread "main" com.uchuhimo.konf.source.LoadException: fail to load DATABASE
	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:519)
	at com.uchuhimo.konf.source.SourceKt$load$1.invoke(Source.kt:532)
	at com.uchuhimo.konf.source.SourceKt$load$1.invoke(Source.kt)
	at com.uchuhimo.konf.BaseConfig.lock(BaseConfig.kt:65)
	at com.uchuhimo.konf.source.SourceKt.load(Source.kt:530)
	at com.uchuhimo.konf.BaseConfig.withSource(BaseConfig.kt:585)
	at com.uchuhimo.konf.source.DefaultLoaders.env(DefaultLoaders.kt:118)
	at example.server.ServerSpecKt.getApplicationConfig(ServerSpec.kt:41)
	at example.StarterAppKt.initializeApplicationContext(StarterApp.kt:43)
	at example.StarterAppKt.main(StarterApp.kt:68)
	at example.StarterAppKt.main(StarterApp.kt)
Caused by: com.uchuhimo.konf.source.ObjectMappingException: unable to map source com.uchuhimo.konf.ContainerNode@33c2bd in [type: system-environment] to value of type String
	at com.uchuhimo.konf.source.SourceKt.toValue(Source.kt:820)
	at com.uchuhimo.konf.source.SourceKt.loadItem(Source.kt:512)
	... 10 more
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize instance of `java.lang.String` out of START_OBJECT token
 at [Source: UNKNOWN; line: -1, column: -1]
	at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
	at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1343)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1139)
	at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1093)
	at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:63)
	at com.fasterxml.jackson.databind.deser.std.StringDeserializer.deserialize(StringDeserializer.java:10)
	at com.fasterxml.jackson.databind.ObjectMapper._readValue(ObjectMapper.java:3985)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2343)
	at com.uchuhimo.konf.source.SourceKt.toValue(Source.kt:815)
	... 11 more

Is there a mode to rely on simple naming without inferred nesting?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.