Skip to content

Commit

Permalink
Custom config parser for more complex config structure (corda#3513)
Browse files Browse the repository at this point in the history
* custom config parser for more complex config structure

* address PR issues
  • Loading branch information
Patrick Kuo authored Jul 4, 2018
1 parent d634cdc commit 68d0826
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,26 @@ import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.KType
import kotlin.reflect.full.createInstance
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.jvmErasure

@Target(AnnotationTarget.PROPERTY)
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"

// TODO Move other config parsing to use parseAs and remove this
Expand All @@ -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 {
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 parameters = constructor.parameters
val parameterNames = parameters.flatMap { param ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
value1: V,
value2: V,
Expand Down Expand Up @@ -310,6 +340,7 @@ class ConfigParsingTest {
require(positive > 0) { "$positive is not positive" }
}
}

data class OldData(
@OldConfig("oldValue")
val newValue: String)
Expand Down

0 comments on commit 68d0826

Please sign in to comment.