Skip to content

Commit

Permalink
Adds to define if defaults are applied to config loader. (#223)
Browse files Browse the repository at this point in the history
* Adds command line property source.

* Adds map property source and tests for map and command line.

* Adds to define if defaults are applied to config loader.

Co-authored-by: Frederic Kneier <[email protected]>
  • Loading branch information
frederic-kneier and frederic-kneier authored Sep 29, 2021
1 parent 4837f59 commit cad34b4
Show file tree
Hide file tree
Showing 4 changed files with 208 additions and 31 deletions.
84 changes: 53 additions & 31 deletions hoplite-core/src/main/kotlin/com/sksamuel/hoplite/ConfigLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

package com.sksamuel.hoplite

import com.sksamuel.hoplite.decoder.*
import com.sksamuel.hoplite.fp.invalid
import com.sksamuel.hoplite.decoder.Decoder
import com.sksamuel.hoplite.decoder.DecoderRegistry
import com.sksamuel.hoplite.decoder.defaultDecoderRegistry
import com.sksamuel.hoplite.fp.flatMap
import com.sksamuel.hoplite.fp.getOrElse
import com.sksamuel.hoplite.fp.invalid
import com.sksamuel.hoplite.fp.sequence
import com.sksamuel.hoplite.parsers.*
import com.sksamuel.hoplite.parsers.Parser
import com.sksamuel.hoplite.parsers.ParserRegistry
import com.sksamuel.hoplite.parsers.defaultParserRegistry
import com.sksamuel.hoplite.preprocessor.Preprocessor
import com.sksamuel.hoplite.preprocessor.defaultPreprocessors
import java.io.File
Expand All @@ -31,6 +35,10 @@ class ConfigLoader constructor(
operator fun invoke(): ConfigLoader {
return Builder().build()
}

inline operator fun invoke(block: Builder.() -> Unit): ConfigLoader {
return Builder().apply(block).build()
}
}

@Deprecated(
Expand Down Expand Up @@ -106,74 +114,77 @@ class ConfigLoader constructor(
private val paramMapperStaging = mutableListOf<ParameterMapper>()
private val failureCallbacks = mutableListOf<(Throwable) -> Unit>()
private var mode: DecodeMode = DecodeMode.Lenient
private var defaultSources = true
private var defaultPreprocessors = true
private var defaultParamMappers = true

fun withDefaultSources(defaultSources: Boolean): Builder = apply {
this.defaultSources = defaultSources
}

fun withDefaultPreprocessors(defaultPreprocessors: Boolean): Builder = apply {
this.defaultPreprocessors = defaultPreprocessors
}

fun withDefaultParamMappers(defaultParamMappers: Boolean): Builder = apply {
this.defaultParamMappers = defaultParamMappers
}

fun withClassLoader(classLoader: ClassLoader): Builder {
fun withClassLoader(classLoader: ClassLoader): Builder = apply {
if (this.classLoader !== classLoader) {
this.classLoader = classLoader
}
return this
}

fun addDecoder(decoder: Decoder<*>): Builder {
fun addDecoder(decoder: Decoder<*>): Builder = apply {
this.decoderStaging.add(decoder)
return this
}

fun addDecoders(decoders: Iterable<Decoder<*>>): Builder {
fun addDecoders(decoders: Iterable<Decoder<*>>): Builder = apply {
this.decoderStaging.addAll(decoders)
return this
}

fun addFileExtensionMapping(ext: String, parser: Parser): Builder {
fun addFileExtensionMapping(ext: String, parser: Parser): Builder = apply {
this.parserStaging[ext] = parser
return this
}

fun addFileExtensionMappins(map: Map<String, Parser>): Builder {
fun addFileExtensionMappins(map: Map<String, Parser>): Builder = apply {
map.forEach {
val (ext, parser) = it
this.parserStaging[ext] = parser
}
return this
}

fun strict(): Builder {
fun strict(): Builder = apply {
this.mode = DecodeMode.Strict
return this
}

fun addSource(source: PropertySource) = addPropertySource(source)

fun addPropertySource(propertySource: PropertySource): Builder {
fun addPropertySource(propertySource: PropertySource): Builder = apply {
this.propertySourceStaging.add(propertySource)
return this
}

fun addSources(sources: Iterable<PropertySource>) = addPropertySources(sources)

fun addPropertySources(propertySources: Iterable<PropertySource>): Builder {
fun addPropertySources(propertySources: Iterable<PropertySource>): Builder = apply {
this.propertySourceStaging.addAll(propertySources)
return this
}

fun addPreprocessor(preprocessor: Preprocessor): Builder {
fun addPreprocessor(preprocessor: Preprocessor): Builder = apply {
this.preprocessorStaging.add(preprocessor)
return this
}

fun addPreprocessors(preprocessors: Iterable<Preprocessor>): Builder {
fun addPreprocessors(preprocessors: Iterable<Preprocessor>): Builder = apply {
this.preprocessorStaging.addAll(preprocessors)
return this
}

fun addParameterMapper(paramMapper: ParameterMapper): Builder {
fun addParameterMapper(paramMapper: ParameterMapper): Builder = apply {
this.paramMapperStaging.add(paramMapper)
return this
}

fun addParameterMappers(paramMappers: Iterable<ParameterMapper>): Builder {
fun addParameterMappers(paramMappers: Iterable<ParameterMapper>): Builder = apply {
this.paramMapperStaging.addAll(paramMappers)
return this
}

/**
Expand Down Expand Up @@ -201,9 +212,20 @@ class ConfigLoader constructor(
}

// other defaults
val propertySources = defaultPropertySources() + this.propertySourceStaging
val preprocessors = defaultPreprocessors() + this.preprocessorStaging
val paramMappers = defaultParamMappers() + this.paramMapperStaging
val propertySources = when {
defaultSources -> defaultPropertySources() + this.propertySourceStaging
else -> this.propertySourceStaging
}

val preprocessors = when {
defaultPreprocessors -> defaultPreprocessors() + this.preprocessorStaging
else -> this.preprocessorStaging
}

val paramMappers = when {
defaultParamMappers -> defaultParamMappers() + this.paramMapperStaging
else -> this.paramMapperStaging
}

return ConfigLoader(
decoderRegistry = decoderRegistry,
Expand Down Expand Up @@ -352,7 +374,7 @@ class ConfigLoader constructor(
private fun loadNode(configs: List<ConfigSource>): ConfigResult<Node> {
val srcs = propertySources + configs.map { ConfigFilePropertySource(it) }
return srcs.map { it.node(PropertySourceContext(parserRegistry)) }.sequence()
.map { it.reduce { acc, b -> acc.merge(b) } }
.map { it.takeUnless { it.isEmpty() }?.reduce { acc, b -> acc.merge(b) } ?: NullNode(Pos.NoPos)}
.mapInvalid {
val multipleFailures = ConfigFailure.MultipleFailures(it)
multipleFailures
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.sksamuel.hoplite

import java.io.File
import java.io.InputStream
import java.nio.file.Path

/**
* Returns a [PropertySource] that will read the specified resource from the classpath.
*
* @param optional if true then the resource can not exist and the config loader will ignore this source
*/
fun ConfigLoader.Builder.addResourceSource(resource: String, optional: Boolean = false) = addSource(
ConfigFilePropertySource(ConfigSource.ClasspathSource(resource), optional = optional)
)

/**
* Returns a [PropertySource] that will read the specified file from the filesystem.
*
* @param optional if true then the resource can not exist and the config loader will ignore this source
*/
fun ConfigLoader.Builder.addFileSource(file: File, optional: Boolean = false) = addSource(
ConfigFilePropertySource(ConfigSource.FileSource(file), optional = optional)
)

/**
* Returns a [PropertySource] that will read the specified resource from the classpath.
*
* @param optional if true then the resource can not exist and the config loader will ignore this source
*/
fun ConfigLoader.Builder.addPathSource(path: Path, optional: Boolean = false) = addSource(
ConfigFilePropertySource(ConfigSource.PathSource(path), optional = optional)
)

/**
* Returns a [PropertySource] that will read the specified input stream.
*
* @param input the input stream to read from
* @param ext the file extension of the input format
*/
fun ConfigLoader.Builder.addStreamSource(input: InputStream, ext: String) = addSource(
InputStreamPropertySource(input, ext)
)

/**
* Returns a [PropertySource] that read the specified map values.
*
* @param map map
*/
fun ConfigLoader.Builder.addMapSource(map: Map<String, Any>) = addSource(
MapPropertySource(map)
)

/**
* Returns a [PropertySource] that will read the specified command line arguments.
*
* @param arguments command line arguments as passed to main method
* @param prefix argument prefix
* @param delimiter key value delimiter
*/
fun ConfigLoader.Builder.addCommandLineSource(
arguments: Array<String>,
prefix: String = "--",
delimiter: String = "=",
) = addSource(
CommandLinePropertySource(arguments, prefix, delimiter)
)

/**
* Returns a [PropertySource] that will read the environment settings.
*
* @param arguments command line arguments as passed to main method
* @param prefix argument prefix
* @param delimiter key value delimiter
*/
fun ConfigLoader.Builder.addEnvironmentSource(
useUnderscoresAsSeparator: Boolean = true,
allowUppercaseNames: Boolean = true,
) = addSource(
EnvironmentVariablesPropertySource(useUnderscoresAsSeparator, allowUppercaseNames)
)

/**
* Returns a [PropertySource] that will read from the specified string.
*
* @param str the string to read from
* @param ext the file extension of the input format
*/
fun ConfigLoader.Builder.string(
str: String, ext: String
) = addStreamSource(str.byteInputStream(), ext)
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,17 @@ interface PropertySource {
fun commandLine(arguments: Array<String>, prefix: String = "--", delimiter: String = "=") =
CommandLinePropertySource(arguments, prefix, delimiter)

/**
* Returns a [PropertySource] that will read the environment settings.
*
* @param arguments command line arguments as passed to main method
* @param prefix argument prefix
* @param delimiter key value delimiter
*/
fun environment(useUnderscoresAsSeparator: Boolean = true, allowUppercaseNames: Boolean = true) =
EnvironmentVariablesPropertySource(useUnderscoresAsSeparator, allowUppercaseNames)


/**
* Returns a [PropertySource] that will read from the specified string.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.sksamuel.hoplite

import com.sksamuel.hoplite.fp.Validated
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.types.instanceOf

class WithoutDefaultsRegistryTest : FunSpec() {
init {
test("default registry throws no error") {
val loader = ConfigLoader {
addMapSource(mapOf("custom_value" to "\${PATH}"))
}
val e = loader.loadConfig<Config>()
e as Validated.Valid<Config>
e.value.customValue shouldNotBe "\${path}"
}

test("empty sources registry throws error") {
val loader = ConfigLoader {
withDefaultSources(false)
addMapSource(mapOf("custom_value" to "\${PATH}"))
}
val e = loader.loadConfig<Config>()
e as Validated.Invalid<ConfigFailure>
e.error shouldBe instanceOf(ConfigFailure.DataClassFieldErrors::class)
}

test("empty param mappers registry throws error") {
val loader = ConfigLoader {
withDefaultParamMappers(false)
addMapSource(mapOf("custom_value" to "\${PATH}"))
}

val e = loader.loadConfig<Config>()
e as Validated.Invalid<ConfigFailure>
e.error shouldBe instanceOf(ConfigFailure.DataClassFieldErrors::class)
}

test("empty preprocessors registry throws error") {
val loader = ConfigLoader {
withDefaultPreprocessors(false)
addMapSource(mapOf("custom_value" to "\${PATH}"))
}
val e = loader.loadConfig<Config>()
e as Validated.Valid<Config>
e.value.customValue shouldBe "\${PATH}"
}

}

data class Config(val PATH: String, val customValue: String)
}

0 comments on commit cad34b4

Please sign in to comment.