Skip to content

Latest commit

 

History

History
164 lines (147 loc) · 5.25 KB

DelegateHelpers.md

File metadata and controls

164 lines (147 loc) · 5.25 KB

Delegate helpers

The ConfigValueSuppliers are the underlying code which does the work, but aren't very nice to use when defining properties. Instead, there are helpers you can use to create property delegates for configuration property members. The examples in the README show these in use. We'll go over the helper methods here:

Simple

To define a simple property which pulls only from a single source, use:

val myProperty: Int by config("path.to.property".from(myConfigSource))

Fallback

To define a property which checks multiple configuration sources, stopping at the first value it finds, use:

val myProperty: Int by config {
    "legacy.path".from(legacyConfigSource)
    "new.path".from(newConfigSource)
}

This will first try to retrieve an Int at legacy.path from legacyConfigSource, if it isn't found, it will try to retrieve an Int at new.path from newConfigSource.


Value/Type transformation

To transform the retrieved value in some way (here, by inverting the retrieved boolean), use:

val myProperty: Boolean by config {
    "path.to.property".from(myConfigSource).andTransformBy { !it }
}

This is useful if the semantics of a property were changed, for example:

old_config.conf

app {
  server {
      enabled = false
  }
}

new_config.conf

app {
    server {
        disabled = false
    }
}

The property would be:

val serverEnabled: Boolean by config {
    "app.server.enabled".from(oldConfig)
    // Invert the value to match if it's 'enabled'
    "app.server.disabled".from(newConfig).andTransformBy { !it }
}

Converting the type of a value is also possible. This is useful if you want the code to use a friendlier type than the config (say a Duration instead of a Long representing milliseconds):

val healthInterval: Duration by config {
    "app.health.interval".from(legacyConfigSource).convertFrom<Long>(Duration::ofMillis)
}

Pulling a value from elsewhere

It's possible to pull a value from anywhere (e.g. a property of an object, or even just a hard-coded default) by passing a lambda:

val port: Int by config {
    "path.to.port".from(myConfig)
    // Since the lambda is opaque, the description gives some context
    "Foo::port" { foo.port }
}

This will first try to retrieve an Int at path.to.port from myConfig and, if it can't be found, will grab the port member of foo. This could also be used to set a default value:

val port: Int by config {
    "path.to.port".from(myConfig)
    // Since the lambda is opaque, the description gives some context
    "default" { 8080 }
}

Conditionally enabling a property

It's possible to guard access to a property if it should only be used based on the value of another property (e.g. a feature being enabled).

The following example will only allow accessing port if serverEnabled evaluates to true. If it doesn't, then ConfigException.UnableToRetrieve.ConditionNotMet will be thrown.

val serverEnabled: Boolean by config("app.server.enabled".from(config))

val port: Int by config {
    onlyIf("Server is enabled", ::serverEnabled) {
        "path.to.port".from(myConfig)
        // Since the lambda is opaque, the description gives some context
        "default" { 8080 }
    }
}

Optional properties

Properties which are optional (meaning they should have a value of null if not found anywhere), can be done like so:

val myProperty: Int? by optionalconfig("path.to.property".from(myConfigSource))

Or

val myProperty: Int? by optionalconfig {
    "path.to.property".from(myConfigSource)
    "new.path.to.property".from(myConfigSource)
}

Deprecation of properties

Say you have the following json config:

config.json

"app": {
  "server": {
    "enabled": "false"
  }
}

And you want to move the property:

config.json

"app": {
  "api": {
    "server": {
      "enabled": "false"
    }
  }
}

You'd define a property in the code to look in both places, so deployments with the old configuration don't break:

val serverEnabled: Boolean by config {
    "app.server.enabled".from(myConfig)
    "app.api.server.enabled".from(myConfig)
}

But you want users to know that app.server.enabled is deprecated and they should use the new name/path. You can mark the old path as deprecated:

val serverEnabled: Boolean by config {
    "app.server.enabled".from(myConfig).softDeprecated("use 'app.api.server.enabled'")
    "app.api.server.enabled".from(myConfig)
}

If a value is retrieved via "app.server.enabled" from myConfig, a warning will be logged:

WARN: Key 'app.server.enabled' from source 'myConfig' is dprecated: use app.api.server.enabled

This warning is only printed once, and only if the value marked as deprecated was used as the "result".

Values can also be hard deprecated:

val serverEnabled: Boolean by config {
    "app.server.enabled".from(myConfig).hardDeprecated("use 'app.api.server.enabled'")
    "app.api.server.enabled".from(myConfig)
}

And ConfigException.UnableToRetrieve.Deprecated will be thrown if that value is used as the result.