mirror of
https://github.com/corda/corda.git
synced 2025-01-11 15:32:49 +00:00
Custom config parser for more complex config structure (#3513)
* custom config parser for more complex config structure * address PR issues
This commit is contained in:
parent
d634cdcbac
commit
68d0826563
@ -25,6 +25,8 @@ import java.util.*
|
|||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.full.createInstance
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.full.primaryConstructor
|
import kotlin.reflect.full.primaryConstructor
|
||||||
import kotlin.reflect.jvm.jvmErasure
|
import kotlin.reflect.jvm.jvmErasure
|
||||||
@ -32,6 +34,17 @@ import kotlin.reflect.jvm.jvmErasure
|
|||||||
@Target(AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.PROPERTY)
|
||||||
annotation class OldConfig(val value: String)
|
annotation class OldConfig(val value: String)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This annotation can be used to provide ConfigParser for the class,
|
||||||
|
* the [parseAs] method will use the provided parser instead of data class constructs to parse the object.
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.CLASS)
|
||||||
|
annotation class CustomConfigParser(val parser: KClass<out ConfigParser<*>>)
|
||||||
|
|
||||||
|
interface ConfigParser<T> {
|
||||||
|
fun parse(config: Config): T
|
||||||
|
}
|
||||||
|
|
||||||
const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
|
const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
|
||||||
|
|
||||||
// TODO Move other config parsing to use parseAs and remove this
|
// TODO Move other config parsing to use parseAs and remove this
|
||||||
@ -40,7 +53,10 @@ operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, nestedPath: String? = null): T {
|
fun <T : Any> Config.parseAs(clazz: KClass<T>, onUnknownKeys: ((Set<String>, logger: Logger) -> Unit) = UnknownConfigKeysPolicy.FAIL::handle, nestedPath: String? = null): T {
|
||||||
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
|
// Use custom parser if provided, instead of treating the object as data class.
|
||||||
|
clazz.findAnnotation<CustomConfigParser>()?.let { return uncheckedCast(it.parser.createInstance().parse(this)) }
|
||||||
|
|
||||||
|
require(clazz.isData) { "Only Kotlin data classes or class annotated with CustomConfigParser can be parsed. Offending: ${clazz.qualifiedName}" }
|
||||||
val constructor = clazz.primaryConstructor!!
|
val constructor = clazz.primaryConstructor!!
|
||||||
val parameters = constructor.parameters
|
val parameters = constructor.parameters
|
||||||
val parameterNames = parameters.flatMap { param ->
|
val parameterNames = parameters.flatMap { param ->
|
||||||
|
@ -227,6 +227,36 @@ class ConfigParsingTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `parse with provided parser`() {
|
||||||
|
val type1Config = mapOf("type" to "1", "value" to "type 1 value")
|
||||||
|
val type2Config = mapOf("type" to "2", "value" to "type 2 value")
|
||||||
|
|
||||||
|
val configuration = config("values" to listOf(type1Config, type2Config))
|
||||||
|
val objects = configuration.parseAs<TestObjects>()
|
||||||
|
|
||||||
|
assertThat(objects.values).containsExactly(TestObject.Type1("type 1 value"), TestObject.Type2("type 2 value"))
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestParser : ConfigParser<TestObject> {
|
||||||
|
override fun parse(config: Config): TestObject {
|
||||||
|
val type = config.getInt("type")
|
||||||
|
return when (type) {
|
||||||
|
1 -> config.parseAs<TestObject.Type1>(onUnknownKeys = UnknownConfigKeysPolicy.IGNORE::handle)
|
||||||
|
2 -> config.parseAs<TestObject.Type2>(onUnknownKeys = UnknownConfigKeysPolicy.IGNORE::handle)
|
||||||
|
else -> throw IllegalArgumentException("Unsupported Object type : '$type'")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class TestObjects(val values: List<TestObject>)
|
||||||
|
|
||||||
|
@CustomConfigParser(TestParser::class)
|
||||||
|
sealed class TestObject {
|
||||||
|
data class Type1(val value: String) : TestObject()
|
||||||
|
data class Type2(val value: String) : TestObject()
|
||||||
|
}
|
||||||
|
|
||||||
private inline fun <reified S : SingleData<V>, reified L : ListData<V>, V : Any> testPropertyType(
|
private inline fun <reified S : SingleData<V>, reified L : ListData<V>, V : Any> testPropertyType(
|
||||||
value1: V,
|
value1: V,
|
||||||
value2: V,
|
value2: V,
|
||||||
@ -310,6 +340,7 @@ class ConfigParsingTest {
|
|||||||
require(positive > 0) { "$positive is not positive" }
|
require(positive > 0) { "$positive is not positive" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class OldData(
|
data class OldData(
|
||||||
@OldConfig("oldValue")
|
@OldConfig("oldValue")
|
||||||
val newValue: String)
|
val newValue: String)
|
||||||
|
Loading…
Reference in New Issue
Block a user