Last active
January 18, 2021 19:10
-
-
Save DRSchlaubi/d74f0bb798f0da091cf0f9306b0883dd to your computer and use it in GitHub Desktop.
Small helper class forenvironment variable based configs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import kotlin.properties.ReadOnlyProperty | |
import kotlin.reflect.KProperty | |
/** | |
* Helper class that allows you to specify a [prefix] for your whole config. | |
* | |
* Is intended to be used via composition or via inheritance | |
*/ | |
public open class EnvironmentConfig internal constructor(private val prefix: String) { | |
/** | |
* @see getEnv | |
*/ | |
protected fun getEnv(default: String? = null): EnvironmentVariable<String> = | |
getEnv(prefix, default) | |
/** | |
* @see getEnv | |
*/ | |
protected fun <T> getEnv( | |
default: T? = null, | |
transform: (String) -> T? | |
): EnvironmentVariable<T> = | |
getEnv(prefix, default, transform) | |
} | |
/** | |
* Returns a delegated environment variable prefixed by [prefix] that fallbacks to [default] if the found variable is empty or invalid | |
*/ | |
private fun getEnv( | |
prefix: String? = null, | |
default: String? = null | |
): EnvironmentVariable<String> = | |
EnvironmentVariable(prefix, { it }, default) | |
/** | |
* Returns a delegated environment variable prefixed by [prefix] that fallbacks to [default] if the found variable is empty or invalid. | |
* | |
* The variable is transformed to [T] by [transform] | |
*/ | |
private fun <T> getEnv( | |
prefix: String? = null, | |
default: T? = null, | |
transform: (String) -> T? | |
): EnvironmentVariable<T> = | |
EnvironmentVariable(prefix, transform, default) | |
/** | |
* Delegated property for a environment variable. | |
* | |
* @param prefix the prefix for the variable | |
* @param transform a transformer to map the value to another type | |
* @param default an optional default value | |
* | |
* @param T the type of the (transformed) variable | |
* | |
* @see getEnv | |
* @see Config | |
* @see ReadOnlyProperty | |
*/ | |
@Suppress("LocalVariableName") | |
public sealed class EnvironmentVariable<T>( | |
private val prefix: String?, | |
protected val transform: (String) -> T?, | |
protected val default: T?, | |
) : ReadOnlyProperty<Any, T> { | |
/** | |
* Computes the name of the variable prefixed by [prefix]. | |
*/ | |
protected val KProperty<*>.prefixedName: String | |
get() = prefix?.let { it + name } ?: name | |
/** | |
* Makes this variable optional. | |
* | |
* @return a new [EnvironmentVariable] being optional | |
*/ | |
public open fun optional(): EnvironmentVariable<T?> = Optional(prefix, transform, default) | |
/** | |
* Internal getter. | |
*/ | |
protected fun <T> getEnv( | |
property: KProperty<*>, | |
default: T? = null, | |
transform: (String) -> T? | |
): T? = System.getenv(property.prefixedName)?.let(transform) ?: default | |
private class Required<T>(prefix: String?, transform: (String) -> T?, default: T?) : | |
EnvironmentVariable<T>(prefix, transform, default) { | |
@Volatile | |
private var _value: T? = null | |
private fun missing(name: String): Nothing = error("Missing env variable: $name") | |
@Suppress("UNCHECKED_CAST") | |
override fun getValue(thisRef: Any, property: KProperty<*>): T { | |
val _v1 = _value | |
if (_v1 != null) { | |
return _v1 | |
} | |
return synchronized(this) { | |
val _v2 = _value | |
if (_v2 != null) { | |
_v2 | |
} else { | |
val typedValue = getEnv(property, default, transform) | |
_value = typedValue | |
typedValue ?: missing(property.prefixedName) | |
} | |
} | |
} | |
} | |
private class Optional<T>(prefix: String?, transform: (String) -> T?, default: T?) : | |
EnvironmentVariable<T?>(prefix, transform, default) { | |
private object UNINITIALIZED | |
@Volatile | |
private var _value: Any? = UNINITIALIZED | |
override fun optional(): EnvironmentVariable<T?> = this | |
@Suppress("UNCHECKED_CAST") | |
override fun getValue(thisRef: Any, property: KProperty<*>): T { | |
val _v1 = _value | |
if (_v1 != UNINITIALIZED) { | |
return _v1 as T | |
} | |
return synchronized(this) { | |
val _v2 = _value | |
if (_v2 != UNINITIALIZED) { | |
_v2 as T | |
} else { | |
val typedValue = getEnv(property, default, transform) | |
_value = typedValue | |
typedValue as T | |
} | |
} | |
} | |
} | |
public companion object { | |
/** | |
* @see EnvironmentVariable | |
*/ | |
public operator fun <T> invoke( | |
prefix: String?, | |
transform: (String) -> T?, | |
default: T?, | |
): EnvironmentVariable<T> = Required(prefix, transform, default) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object Config { | |
// => getenv("ENV_VAR") | |
val ENV_VAR: String by getEnv() | |
// => getenv("PREFIX_ENV_VAR") | |
val ENV_VAR2: String? by getEnv("PREFIX_").optional() | |
val TIMEOUT: Int by getEnv { | |
it.toIntOrNull() // an error is thrown when the type is invalid and there is no default } | |
} | |
val DEFAULTED_TIMEOUT by getEnv(default = 10) { it.toIntOrNull() } | |
} | |
object MyConfig : Config("SERVICE_") { | |
// => getenv("SERVICE_ENV_VAR") | |
val ENV_VAR: String by getEnv() | |
val TIMEOUT: Int by getEnv { | |
it.toIntOrNull() // an error is thrown when the type is invalid and there is no default } | |
} | |
val DEFAULTED_TIMEOUT: Int by getEnv(default = 10) { it.toIntOrNull() } | |
} | |
object ComposedConfig { | |
val config = Config("SERVICE_") | |
// => getenv("SERVICE_ENV_VAR") | |
val ENV_VAR: String by config.getEnv() | |
val TIMEOUT: Int by config.getEnv { | |
it.toIntOrNull() // an error is thrown when the type is invalid and there is no default } | |
} | |
val DEFAULTED_TIMEOUT: Int by config.getEnv(default = 10) { it.toIntOrNull() } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment