[CORDA-2244]: Allow map and mapValid on entire lists wrt configuration parsing (#4280)

This commit is contained in:
Michele Sollecito 2018-11-23 15:11:59 +00:00 committed by GitHub
parent 8f15cfaa28
commit a5fb1a82f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 293 additions and 50 deletions

View File

@ -1,10 +1,6 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.*
import net.corda.common.configuration.parsing.internal.versioned.VersionExtractor
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
@ -25,7 +21,7 @@ object Configuration {
/**
* Describes a [Config] hiding sensitive data.
*/
fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }): ConfigValue?
fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue = { value -> ConfigValueFactory.fromAnyRef(value.toString()) }): ConfigValue?
}
object Value {
@ -131,6 +127,22 @@ object Configuration {
fun optional(): Optional<TYPE>
}
/**
* Defines a required property with a collection of values.
*/
interface RequiredList<TYPE> : Required<List<TYPE>> {
/**
* Passes the value to a validating mapping function, provided this is valid in the first place.
*/
fun <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Validated<MAPPED, Validation.Error>): Required<MAPPED>
/**
* Passes the value to a non-validating mapping function, provided this is valid in the first place.
*/
fun <MAPPED> map(mappedTypeName: String, convert: (List<TYPE>) -> MAPPED): Required<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
}
/**
* Defines a property that must provide a single value or produce an error in case multiple values are specified for the relevant key.
*/
@ -139,7 +151,7 @@ object Configuration {
/**
* Returns a required property expecting multiple values for the relevant key.
*/
fun list(): Required<List<TYPE>>
fun list(): RequiredList<TYPE>
}
/**
@ -161,12 +173,12 @@ object Configuration {
/**
* Passes the value to a validating mapping function, provided this is valid in the first place.
*/
fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Validated<MAPPED, Validation.Error>): Standard<MAPPED>
fun <MAPPED> mapValid(mappedTypeName: String, convert: (TYPE) -> Validated<MAPPED, Validation.Error>): Standard<MAPPED>
/**
* Passes the value to a non-validating mapping function, provided this is valid in the first place.
*/
fun <MAPPED : Any> map(mappedTypeName: String, convert: (TYPE) -> MAPPED): Standard<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
fun <MAPPED> map(mappedTypeName: String, convert: (TYPE) -> MAPPED): Standard<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
}
override fun parse(configuration: Config, options: Configuration.Validation.Options): Validated<TYPE, Validation.Error> {
@ -268,7 +280,7 @@ object Configuration {
*/
fun validate(target: Config): Valid<Config> = validate(target, Configuration.Validation.Options.defaults)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue
companion object {
@ -358,7 +370,7 @@ object Configuration {
override fun validate(target: Config, options: Validation.Options) = schema.validate(target, options)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = schema.describe(configuration, serialiseValue)
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue) = schema.describe(configuration, serialiseValue)
final override fun parse(configuration: Config, options: Configuration.Validation.Options): Valid<VALUE> = validate(configuration, options).mapValid(::parseValid)

View File

@ -1,10 +1,6 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigObject
import com.typesafe.config.ConfigValue
import com.typesafe.config.ConfigValueFactory
import com.typesafe.config.*
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import net.corda.common.validation.internal.Validated.Companion.valid
@ -27,13 +23,13 @@ internal open class StandardProperty<TYPE : Any>(override val key: String, typeN
override val typeName: String = schema?.let { "#${it.name ?: "Object@$key"}" } ?: typeNameArg
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = FunctionalProperty(this, mappedTypeName, extractListValue, convert)
override fun <MAPPED> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = FunctionalProperty(this, mappedTypeName, extractListValue, convert)
override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
override fun list(): Configuration.Property.Definition.Required<List<TYPE>> = ListProperty(this)
override fun list(): Configuration.Property.Definition.RequiredList<TYPE> = ListProperty(this)
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
if (isSensitive) {
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
@ -61,7 +57,7 @@ internal open class StandardProperty<TYPE : Any>(override val key: String, typeN
override fun toString() = "\"$key\": \"$typeName\""
}
private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate) {
private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : RequiredDelegatedProperty<List<TYPE>, StandardProperty<TYPE>>(delegate), Configuration.Property.Definition.RequiredList<TYPE> {
override val typeName: String = "List<${delegate.typeName}>"
@ -79,7 +75,9 @@ private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : Requi
return Validated.withResult(target, errors)
}
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
override fun <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Validated<MAPPED, Configuration.Validation.Error>): Configuration.Property.Definition.Required<MAPPED> = ListMappingProperty(this, mappedTypeName, convert)
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
if (isSensitive) {
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
@ -102,28 +100,28 @@ private class ListProperty<TYPE : Any>(delegate: StandardProperty<TYPE>) : Requi
}
}
private class OptionalPropertyWithDefault<TYPE : Any>(delegate: Configuration.Property.Definition.Optional<TYPE>, private val defaultValue: TYPE) : DelegatedProperty<TYPE, Configuration.Property.Definition.Optional<TYPE>>(delegate) {
private class OptionalPropertyWithDefault<TYPE>(delegate: Configuration.Property.Definition.Optional<TYPE>, private val defaultValue: TYPE) : DelegatedProperty<TYPE, Configuration.Property.Definition.Optional<TYPE>>(delegate) {
override val isMandatory: Boolean = false
override val typeName: String = delegate.typeName.removeSuffix("?")
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue) ?: valueDescription(if (isSensitive) Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER else defaultValue, serialiseValue)
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue) ?: valueDescription(if (isSensitive) Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER else defaultValue, serialiseValue)
override fun valueIn(configuration: Config): TYPE = delegate.valueIn(configuration) ?: defaultValue
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> = delegate.validate(target, options)
}
private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
private class FunctionalProperty<TYPE, MAPPED>(delegate: Configuration.Property.Definition.Standard<TYPE>, private val mappedTypeName: String, internal val extractListValue: (Config, String) -> List<TYPE>, private val convert: (TYPE) -> Valid<MAPPED>) : RequiredDelegatedProperty<MAPPED, Configuration.Property.Definition.Standard<TYPE>>(delegate), Configuration.Property.Definition.Standard<MAPPED> {
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).value()
override val typeName: String = if (super.typeName == "#$mappedTypeName") super.typeName else "$mappedTypeName(${super.typeName})"
override fun <M : Any> mapValid(mappedTypeName: String, convert: (MAPPED) -> Valid<M>): Configuration.Property.Definition.Standard<M> = FunctionalProperty(delegate, mappedTypeName, extractListValue, { target: TYPE -> this.convert.invoke(target).mapValid(convert) })
override fun <M> mapValid(mappedTypeName: String, convert: (MAPPED) -> Valid<M>): Configuration.Property.Definition.Standard<M> = FunctionalProperty(delegate, mappedTypeName, extractListValue, { target: TYPE -> this.convert.invoke(target).mapValid(convert) })
override fun list(): Configuration.Property.Definition.Required<List<MAPPED>> = FunctionalListProperty(this)
override fun list(): Configuration.Property.Definition.RequiredList<MAPPED> = FunctionalListProperty(this)
override fun validate(target: Config, options: Configuration.Validation.Options): Valid<Config> {
@ -135,10 +133,10 @@ private class FunctionalProperty<TYPE, MAPPED : Any>(delegate: Configuration.Pro
return Validated.withResult(target, errors)
}
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = delegate.describe(configuration, serialiseValue)
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue) = delegate.describe(configuration, serialiseValue)
}
private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProperty<RAW, TYPE>) : RequiredDelegatedProperty<List<TYPE>, FunctionalProperty<RAW, TYPE>>(delegate) {
private class FunctionalListProperty<RAW, TYPE>(delegate: FunctionalProperty<RAW, TYPE>) : RequiredDelegatedProperty<List<TYPE>, FunctionalProperty<RAW, TYPE>>(delegate), Configuration.Property.Definition.RequiredList<TYPE> {
override val typeName: String = "List<${super.typeName}>"
@ -167,13 +165,15 @@ private class FunctionalListProperty<RAW, TYPE : Any>(delegate: FunctionalProper
}
}
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
if (isSensitive) {
return valueDescription(Configuration.Property.Definition.SENSITIVE_DATA_PLACEHOLDER, serialiseValue)
}
return delegate.schema?.let { schema -> valueDescription(valueIn(configuration).asSequence().map { element -> valueDescription(element, serialiseValue) }.map { it as ConfigObject }.map(ConfigObject::toConfig).map { schema.describe(it, serialiseValue) }.toList(), serialiseValue) } ?: valueDescription(valueIn(configuration), serialiseValue)
}
override fun <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Validated<MAPPED, Configuration.Validation.Error>): Configuration.Property.Definition.Required<MAPPED> = ListMappingProperty(this, mappedTypeName, convert)
}
private abstract class DelegatedProperty<TYPE, DELEGATE : Configuration.Property.Metadata>(protected val delegate: DELEGATE) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition<TYPE> {
@ -181,13 +181,13 @@ private abstract class DelegatedProperty<TYPE, DELEGATE : Configuration.Property
final override fun toString() = "\"$key\": \"$typeName\""
}
private class OptionalDelegatedProperty<TYPE : Any>(private val delegate: Configuration.Property.Definition<TYPE>) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition.Optional<TYPE> {
private class OptionalDelegatedProperty<TYPE>(private val delegate: Configuration.Property.Definition<TYPE>) : Configuration.Property.Metadata by delegate, Configuration.Property.Definition.Optional<TYPE> {
override val isMandatory: Boolean = false
override val typeName: String = "${delegate.typeName}?"
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue) = if (isSpecifiedBy(configuration)) delegate.describe(configuration, serialiseValue) else null
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue) = if (isSpecifiedBy(configuration)) delegate.describe(configuration, serialiseValue) else null
override fun valueIn(configuration: Config): TYPE? {
@ -214,11 +214,39 @@ private class OptionalDelegatedProperty<TYPE : Any>(private val delegate: Config
}
private abstract class RequiredDelegatedProperty<TYPE : Any, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
private abstract class RequiredDelegatedProperty<TYPE, DELEGATE : Configuration.Property.Definition.Required<*>>(delegate: DELEGATE) : DelegatedProperty<TYPE, DELEGATE>(delegate), Configuration.Property.Definition.Required<TYPE> {
final override fun optional(): Configuration.Property.Definition.Optional<TYPE> = OptionalDelegatedProperty(this)
}
private class ListMappingProperty<TYPE, MAPPED>(private val delegate: Configuration.Property.Definition.RequiredList<TYPE>, private val mappedTypeName: String, private val convert: (List<TYPE>) -> Validated<MAPPED, Configuration.Validation.Error>) : Configuration.Property.Definition.Required<MAPPED> {
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue? = delegate.describe(configuration, serialiseValue)
override fun valueIn(configuration: Config) = convert.invoke(delegate.valueIn(configuration)).value()
override fun optional(): Configuration.Property.Definition.Optional<MAPPED> = OptionalDelegatedProperty(this)
override fun validate(target: Config, options: Configuration.Validation.Options): Validated<Config, Configuration.Validation.Error> {
val errors = mutableSetOf<Configuration.Validation.Error>()
errors += delegate.validate(target, options).errors
if (errors.isEmpty()) {
errors += convert.invoke(delegate.valueIn(target)).mapErrors { error -> error.with(delegate.key, mappedTypeName) }.errors
}
return Validated.withResult(target, errors)
}
override val typeName: String = mappedTypeName
override val key = delegate.key
override val isMandatory = delegate.isMandatory
override val isSensitive = delegate.isSensitive
override val schema = delegate.schema
override fun toString() = "\"$key\": \"$typeName\""
}
fun ConfigException.toValidationError(keyName: String? = null, typeName: String): Configuration.Validation.Error {
val toError = when (this) {
@ -249,4 +277,4 @@ private val expectedExceptionTypes = setOf(ConfigException.Missing::class, Confi
private fun isErrorExpected(error: ConfigException) = expectedExceptionTypes.any { expected -> expected.isInstance(error) }
private fun valueDescription(value: Any, serialiseValue: (Any) -> ConfigValue) = serialiseValue.invoke(value)
private fun valueDescription(value: Any?, serialiseValue: (Any?) -> ConfigValue) = serialiseValue.invoke(value)

View File

@ -47,7 +47,7 @@ internal class Schema(override val name: String?, unorderedProperties: Iterable<
return description.toString()
}
override fun describe(configuration: Config, serialiseValue: (Any) -> ConfigValue): ConfigValue {
override fun describe(configuration: Config, serialiseValue: (Any?) -> ConfigValue): ConfigValue {
return properties.asSequence().map { it.key to it.describe(configuration, serialiseValue) }.filter { it.second != null }.fold(configObject()) { config, (key, value) -> config.withValue(key, value) }
}

View File

@ -17,11 +17,20 @@ interface PropertyDelegate<TYPE> {
fun optional(): PropertyDelegate.Optional<TYPE>
}
interface RequiredList<TYPE>: Required<List<TYPE>> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.RequiredList<TYPE>>
fun <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Valid<MAPPED>): Required<MAPPED>
fun <MAPPED> map(mappedTypeName: String, convert: (List<TYPE>) -> MAPPED): Required<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
}
interface Single<TYPE> {
operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Single<TYPE>>
fun list(): Required<List<TYPE>>
fun list(): RequiredList<TYPE>
}
interface Optional<TYPE> {
@ -35,9 +44,9 @@ interface PropertyDelegate<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Standard<TYPE>>
fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Standard<MAPPED>
fun <MAPPED> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): Standard<MAPPED>
fun <MAPPED : Any> map(mappedTypeName: String, convert: (TYPE) -> MAPPED): Standard<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
fun <MAPPED> map(mappedTypeName: String, convert: (TYPE) -> MAPPED): Standard<MAPPED> = mapValid(mappedTypeName) { value -> valid(convert.invoke(value)) }
}
companion object {
@ -74,11 +83,26 @@ private class PropertyDelegateImpl<TYPE>(private val key: String?, private val p
}
}
override fun list(): PropertyDelegate.Required<List<TYPE>> = ListPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() })
override fun list(): PropertyDelegate.RequiredList<TYPE> = ListPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).list() })
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
override fun <MAPPED : Any> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): PropertyDelegate.Standard<MAPPED> = PropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).mapValid(mappedTypeName) { value -> convert.invoke(value) } })
override fun <MAPPED> mapValid(mappedTypeName: String, convert: (TYPE) -> Valid<MAPPED>): PropertyDelegate.Standard<MAPPED> = PropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).mapValid(mappedTypeName) { value -> convert.invoke(value) } })
}
private class RequiredPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Required<TYPE>) : PropertyDelegate.Required<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
val shortName = key ?: property.name
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Required<TYPE> = prop
}
}
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
}
private class OptionalPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Optional<TYPE>) : PropertyDelegate.Optional<TYPE> {
@ -109,17 +133,19 @@ private class OptionalWithDefaultPropertyDelegateImpl<TYPE>(private val key: Str
}
}
private class ListPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.Required<TYPE>) : PropertyDelegate.Required<TYPE> {
private class ListPropertyDelegateImpl<TYPE>(private val key: String?, private val prefix: String?, private val sensitive: Boolean = false, private val addToProperties: (Configuration.Property.Definition<*>) -> Unit, private val construct: (String, Boolean) -> Configuration.Property.Definition.RequiredList<TYPE>) : PropertyDelegate.RequiredList<TYPE> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
override operator fun provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, Configuration.Property.Definition.RequiredList<TYPE>> {
val shortName = key ?: property.name
val prop = construct.invoke(prefix?.let { "$prefix.$shortName" } ?: shortName, sensitive).also(addToProperties)
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition.Required<TYPE>> {
return object : ReadOnlyProperty<Any?, Configuration.Property.Definition.RequiredList<TYPE>> {
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.Required<TYPE> = prop
override fun getValue(thisRef: Any?, property: KProperty<*>): Configuration.Property.Definition.RequiredList<TYPE> = prop
}
}
override fun optional(): PropertyDelegate.Optional<TYPE> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
override fun optional(): PropertyDelegate.Optional<List<TYPE>> = OptionalPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).optional() })
override fun <MAPPED> mapValid(mappedTypeName: String, convert: (List<TYPE>) -> Valid<MAPPED>): PropertyDelegate.Required<MAPPED> = RequiredPropertyDelegateImpl(key, prefix, sensitive, addToProperties, { k, s -> construct.invoke(k, s).mapValid(mappedTypeName) { value -> convert.invoke(value) } })
}

View File

@ -3,13 +3,23 @@ package net.corda.common.configuration.parsing.internal
import com.typesafe.config.*
import net.corda.common.validation.internal.Validated
inline fun <TYPE, reified MAPPED : Any> Configuration.Property.Definition.Standard<TYPE>.mapValid(noinline convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = mapValid(MAPPED::class.java.simpleName, convert)
inline fun <reified ENUM : Enum<ENUM>, VALUE : Any> Configuration.Specification<VALUE>.enum(key: String? = null, sensitive: Boolean = false): PropertyDelegate.Standard<ENUM> = enum(key, ENUM::class, sensitive)
inline fun <TYPE, reified MAPPED : Any> PropertyDelegate.Standard<TYPE>.mapValid(noinline convert: (TYPE) -> Valid<MAPPED>): PropertyDelegate.Standard<MAPPED> = mapValid(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> PropertyDelegate.Standard<TYPE>.mapValid(noinline convert: (TYPE) -> Valid<MAPPED>): PropertyDelegate.Standard<MAPPED> = mapValid(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED : Any> PropertyDelegate.Standard<TYPE>.map(noinline convert: (TYPE) -> MAPPED): PropertyDelegate.Standard<MAPPED> = map(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> PropertyDelegate.Standard<TYPE>.map(noinline convert: (TYPE) -> MAPPED): PropertyDelegate.Standard<MAPPED> = map(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> PropertyDelegate.RequiredList<TYPE>.mapValid(noinline convert: (List<TYPE>) -> Valid<MAPPED>): PropertyDelegate.Required<MAPPED> = mapValid(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> PropertyDelegate.RequiredList<TYPE>.map(noinline convert: (List<TYPE>) -> MAPPED): PropertyDelegate.Required<MAPPED> = map(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> Configuration.Property.Definition.Standard<TYPE>.mapValid(noinline convert: (TYPE) -> Valid<MAPPED>): Configuration.Property.Definition.Standard<MAPPED> = mapValid(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> Configuration.Property.Definition.Standard<TYPE>.map(noinline convert: (TYPE) -> MAPPED): Configuration.Property.Definition.Standard<MAPPED> = map(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> Configuration.Property.Definition.RequiredList<TYPE>.mapValid(noinline convert: (List<TYPE>) -> Valid<MAPPED>): Configuration.Property.Definition.Required<MAPPED> = mapValid(MAPPED::class.java.simpleName, convert)
inline fun <TYPE, reified MAPPED> Configuration.Property.Definition.RequiredList<TYPE>.map(noinline convert: (List<TYPE>) -> MAPPED): Configuration.Property.Definition.Required<MAPPED> = map(MAPPED::class.java.simpleName, convert)
operator fun <TYPE> Config.get(property: Configuration.Property.Definition<TYPE>): TYPE = property.valueIn(this)

View File

@ -4,6 +4,7 @@ import com.typesafe.config.ConfigException
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import java.util.concurrent.atomic.AtomicLong
class PropertyTest {
@ -56,6 +57,68 @@ class PropertyTest {
assertThat(property.valueIn(configuration)).isEqualTo(value)
}
@Test
fun present_value_of_list_type_with_whole_list_mapping() {
val key = "a.b.c"
val value = listOf(1L, 3L, 2L)
val configuration = configObject(key to value).toConfig()
val property = Configuration.Property.Definition.long(key).list().map { list -> list.max() }
println(property)
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
assertThat(property.valueIn(configuration)).isEqualTo(value.max())
}
@Test
fun absent_value_of_list_type_with_whole_list_mapping() {
val key = "a.b.c"
val configuration = configObject().toConfig()
val property = Configuration.Property.Definition.long(key).list().map { list -> list.max() }.optional()
println(property)
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
assertThat(property.valueIn(configuration)).isEqualTo(null)
}
@Test
fun present_value_of_list_type_with_single_element_and_whole_list_mapping() {
val key = "a.b.c"
val value = listOf(1L, 3L, 2L)
val configuration = configObject(key to value).toConfig()
val property = Configuration.Property.Definition.long(key).map(::AtomicLong).list().map { list -> list.map(AtomicLong::get).max() }
println(property)
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isTrue()
assertThat(property.isSpecifiedBy(configuration)).isTrue()
assertThat(property.valueIn(configuration)).isEqualTo(value.max())
}
@Test
fun absent_value_of_list_type_with_single_element_and_whole_list_mapping() {
val key = "a.b.c"
val configuration = configObject().toConfig()
val property = Configuration.Property.Definition.long(key).map(::AtomicLong).list().map { list -> list.map(AtomicLong::get).max() }.optional()
println(property)
assertThat(property.key).isEqualTo(key)
assertThat(property.isMandatory).isFalse()
assertThat(property.isSpecifiedBy(configuration)).isFalse()
assertThat(property.valueIn(configuration)).isEqualTo(null)
}
@Test
fun optional_present_value_of_list_type() {

View File

@ -83,6 +83,48 @@ class PropertyValidationTest {
}
}
@Test
fun whole_list_validation_valid_value() {
val key = "a.b.c"
val value = listOf(1L, 2L, 3L)
val configuration = configObject(key to value).toConfig()
fun parseMax(list: List<Long>): Valid<Long?> = valid(list.max())
val property = Configuration.Property.Definition.long(key).list().mapValid(::parseMax)
assertThat(property.validate(configuration).errors).isEmpty()
}
@Test
fun whole_list_validation_invalid_value() {
val key = "a.b.c"
val value = listOf(1L, 2L, 3L)
val configuration = configObject(key to value).toConfig()
fun parseMax(list: List<Long>): Valid<Long?> {
if (list.any { value -> value <= 1L }) {
return invalid(Configuration.Validation.Error.BadValue.of("All values must be greater than 1"))
}
return valid(list.max())
}
val property = Configuration.Property.Definition.long(key).list().mapValid(::parseMax)
assertThat(property.validate(configuration).errors).satisfies { errors ->
assertThat(errors).hasSize(1)
assertThat(errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.BadValue::class.java) { error ->
assertThat(error.keyName).isEqualTo(key.split(".").last())
assertThat(error.path).containsExactly(*key.split(".").toTypedArray())
}
}
}
@Test
fun wrong_type() {

View File

@ -1,8 +1,11 @@
package net.corda.common.configuration.parsing.internal
import com.typesafe.config.Config
import net.corda.common.validation.internal.Validated
import net.corda.common.validation.internal.Validated.Companion.invalid
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.util.concurrent.atomic.AtomicLong
class SpecificationTest {
@ -50,6 +53,28 @@ class SpecificationTest {
}
}
@Test
fun parse_list_aggregation() {
val spec = object : Configuration.Specification<AtomicLong>("AtomicLong") {
private val maxElement by long("elements").list().map { elements -> elements.max() }
override fun parseValid(configuration: Config): Valid<AtomicLong> {
return valid(AtomicLong(configuration[maxElement]!!))
}
}
val elements = listOf(1L, 10L, 2L)
val configuration = configObject("elements" to elements).toConfig()
val result = spec.parse(configuration)
assertThat(result.isValid).isTrue()
assertThat(result.value().get()).isEqualTo(elements.max())
}
@Test
fun validate() {
@ -68,6 +93,43 @@ class SpecificationTest {
}
}
@Test
fun validate_list_aggregation() {
fun parseMax(elements: List<Long>): Valid<Long> {
if (elements.isEmpty()) {
return invalid(Configuration.Validation.Error.BadValue.of("element list cannot be empty"))
}
if (elements.any { element -> element <= 1 }) {
return invalid(Configuration.Validation.Error.BadValue.of("elements cannot be less than or equal to 1"))
}
return valid(elements.max()!!)
}
val spec = object : Configuration.Specification<AtomicLong>("AtomicLong") {
private val maxElement by long("elements").list().mapValid(::parseMax)
override fun parseValid(configuration: Config): Valid<AtomicLong> {
return valid(AtomicLong(configuration[maxElement]))
}
}
val elements = listOf(1L, 10L, 2L)
val configuration = configObject("elements" to elements).toConfig()
val result = spec.parse(configuration)
assertThat(result.errors).hasSize(1)
assertThat(result.errors.first()).isInstanceOfSatisfying(Configuration.Validation.Error.BadValue::class.java) { error ->
assertThat(error.path).containsExactly("elements")
assertThat(error.message).contains("elements cannot be less than or equal to 1")
}
}
@Test
fun validate_with_domain_specific_errors() {

View File

@ -224,7 +224,7 @@ private fun <T : Enum<T>> enumBridge(clazz: Class<T>, name: String): T {
*/
fun Any.toConfig(): Config = ConfigValueFactory.fromMap(toConfigMap()).toConfig()
fun Any.toConfigValue(): ConfigValue = if (this is ConfigValue) this else ConfigValueFactory.fromAnyRef(convertValue(this))
fun Any?.toConfigValue(): ConfigValue = if (this is ConfigValue) this else if (this != null) ConfigValueFactory.fromAnyRef(convertValue(this)) else ConfigValueFactory.fromAnyRef(null)
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
// Reflect over the fields of the receiver and generate a value Map that can use to create Config object.

View File

@ -26,7 +26,7 @@ internal class ValidateConfigurationCli : CliWrapperBase("validate-configuration
return "for path: \"$pathAsString\": $message"
}
internal fun logRawConfig(config: Config) = logger.debug("Actual configuration:\n${V1NodeConfigurationSpec.describe(config, Any::toConfigValue).render(configRenderingOptions)}")
internal fun logRawConfig(config: Config) = logger.debug("Actual configuration:\n${V1NodeConfigurationSpec.describe(config, Any?::toConfigValue).render(configRenderingOptions)}")
}
@Mixin