mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
[CORDA-2244]: Allow map
and mapValid
on entire lists wrt configuration parsing (#4280)
This commit is contained in:
parent
8f15cfaa28
commit
a5fb1a82f1
@ -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)
|
||||
|
||||
|
@ -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)
|
@ -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) }
|
||||
}
|
||||
|
@ -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) } })
|
||||
}
|
@ -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)
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user