diff --git a/CHANGELOG.md b/CHANGELOG.md index f75a0d56..3d336d11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ This list is not intended to be all-encompassing - it will document major and breaking API changes with their rationale when appropriate: +### v2.11.1.0 +- **data4k** : Add support for Value classes and removed inner class structures to tidy up. + ### v2.11.0.0 - **data4k** : [New module] [data4k](https://github.com/fork-handles/forkhandles/blob/trunk/data4k/README.md): Typesafe data-oriented programming. diff --git a/data4k/README.md b/data4k/README.md index 619b6665..14db0383 100644 --- a/data4k/README.md +++ b/data4k/README.md @@ -6,7 +6,7 @@ GitHub license codebeat badge -Library to make working with Data-Oriented programming in Kotlin easier, to extract values from dynamic data structures such as Maps. +Library to make working with Data-Oriented programming in Kotlin easier, to extract typed values from dynamic data structures such as Maps. ## Installation @@ -19,7 +19,7 @@ implementation("dev.forkhandles:data4k") ## Usage -The library defines a DataContainer class and implementations for: +The library defines a `DataContainer` class and implementations for: - Map - Jackson JSON Node diff --git a/data4k/build.gradle b/data4k/build.gradle index 6655030d..5d0b81e6 100644 --- a/data4k/build.gradle +++ b/data4k/build.gradle @@ -2,6 +2,7 @@ description = 'ForkHandles data-oriented programming library' dependencies { api("org.jetbrains.kotlin:kotlin-reflect:_") + api(project(":values4k")) compileOnly("com.fasterxml.jackson.core:jackson-databind:_") testImplementation("com.fasterxml.jackson.core:jackson-databind:_") testImplementation("io.strikt:strikt-jvm:_") diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt b/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt index b8dbe6ac..ade43027 100644 --- a/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt +++ b/data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt @@ -1,10 +1,13 @@ package dev.forkhandles.data +import dev.forkhandles.values.Value +import dev.forkhandles.values.ValueFactory + /** * Superclass for all container implementations. Defines the delegate property classes to extract data from the * underlying data structure. */ -@Suppress("UNCHECKED_CAST", "ClassName") +@Suppress("UNCHECKED_CAST") abstract class DataContainer( protected val data: CONTENT, existsFn: (CONTENT, String) -> Boolean, @@ -13,11 +16,25 @@ abstract class DataContainer( private val exists: DataContainer.(String) -> Boolean = { existsFn(data, it) } private val get: DataContainer.(String) -> Any? = { getFn(data, it) } - inner class field : DataProperty, OUT>(exists, get) + fun field() = DataProperty, OUT>(exists, get) + + fun field(mapFn: (OUT) -> NEXT) = DataProperty, NEXT>(exists) { + (get(it) as OUT).let(mapFn) + } + + fun > field(factory: ValueFactory) = + DataProperty, OUT>(exists) { (get(it) as IN).let(factory::of) } + + fun list(mapFn: (IN) -> OUT) = + DataProperty, List>(exists) { (get(it) as List).map(mapFn) } + + fun > list(factory: ValueFactory) = + DataProperty, List>(exists) { (get(it) as List).map(factory::of) } + + fun list() = list { it } - inner class list(mapFn: (IN) -> OUT) : - DataProperty, List>(exists, { (get(it) as List).map(mapFn) }) + fun > obj(mapFn: (CONTENT) -> OUT) = + DataProperty, OUT>(exists) { mapFn(get(it) as CONTENT) } - inner class obj>(mapFn: (CONTENT) -> OUT) : - DataProperty, OUT>(exists, { mapFn(get(it) as CONTENT) }) + fun obj() = DataProperty, CONTENT>(exists) { get(it) as CONTENT } } diff --git a/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt b/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt index 7a98ba00..f1f13b60 100644 --- a/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt +++ b/data4k/src/main/kotlin/dev/forkhandles/data/DataProperty.kt @@ -4,7 +4,7 @@ import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import kotlin.reflect.jvm.jvmErasure -abstract class DataProperty( +open class DataProperty( private val existsFn: IN.(String) -> Boolean, private val getFn: IN.(String) -> Any? ) : ReadOnlyProperty { diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt index b2d503e5..66763bee 100644 --- a/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt +++ b/data4k/src/test/kotlin/dev/forkhandles/lens/DataContainerContract.kt @@ -1,5 +1,7 @@ package dev.forkhandles.lens +import dev.forkhandles.values.IntValue +import dev.forkhandles.values.IntValueFactory import org.junit.jupiter.api.Test import strikt.api.expectThat import strikt.api.expectThrows @@ -15,10 +17,14 @@ interface MainClassFields { val notAStringField: String val optionalField: String? val noSuchField: String + val listField: List val listSubClassField: List val listIntsField: List + val listValueField: List val listStringsField: List val objectField: SubClassFields + val valueField: MyType + val mappedField: Int } interface SubClassFields { @@ -26,6 +32,10 @@ interface SubClassFields { val noSuchField: String } +class MyType private constructor(value: Int) : IntValue(value) { + companion object : IntValueFactory(::MyType) +} + abstract class DataContainerContract { abstract fun container(input: Map): MainClassFields @@ -41,6 +51,11 @@ abstract class DataContainerContract { "longField" to Long.MAX_VALUE, "decimalField" to 1.1234, "notAStringField" to 123, + "valueField" to 123, + "mappedField" to "123", + "listField" to listOf("hello"), + "listField" to listOf("hello"), + "listValueField" to listOf(1, 2, 3), "listIntsField" to listOf(1, 2, 3), "listSubClassField" to listOf( mapOf("stringField" to "string1"), @@ -59,7 +74,11 @@ abstract class DataContainerContract { expectThat(input.intField).isEqualTo(123) expectThat(input.longField).isEqualTo(Long.MAX_VALUE) expectThat(input.decimalField).isEqualTo(1.1234) + expectThat(input.valueField).isEqualTo(MyType.of(123)) + expectThat(input.mappedField).isEqualTo(123) + expectThat(input.listField).isEqualTo(listOf("hello")) expectThat(input.listIntsField).isEqualTo(listOf(1, 2, 3)) + expectThat(input.listValueField).isEqualTo(listOf(1, 2, 3).map(MyType::of)) expectThat(input.listSubClassField.map { it.stringField }).isEqualTo(listOf("string1", "string2")) expectThat(input.listStringsField).isEqualTo(listOf("string1", "string2")) expectThat(input.objectField.stringField).isEqualTo("string") diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt index b5cd6dd9..1aad5ac2 100644 --- a/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt +++ b/data4k/src/test/kotlin/dev/forkhandles/lens/JacksonDataContainerTest.kt @@ -22,8 +22,12 @@ class JacksonDataContainerTest : DataContainerContract() { override val noSuchField by field() override val listSubClassField by list(::SubNodeBacked) override val listStringsField by list(Any::toString) - override val listIntsField by list { it as Int } + override val listField by list() + override val listIntsField by list() + override val listValueField by list(MyType) override val objectField by obj(::SubNodeBacked) + override val valueField by field(MyType) + override val mappedField by field(String::toInt) } override fun container(input: Map) = NodeBacked(ObjectMapper().valueToTree(input)) diff --git a/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt b/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt index 11f14c96..38d63720 100644 --- a/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt +++ b/data4k/src/test/kotlin/dev/forkhandles/lens/MapDataContainerTest.kt @@ -18,10 +18,14 @@ class MapDataContainerTest : DataContainerContract() { override val decimalField by field() override val notAStringField by field() override val noSuchField by field() + override val listField by list() + override val listValueField by list(MyType) override val listSubClassField by list(::SubMap) override val listStringsField by list(Any::toString) - override val listIntsField by list { it as Int } + override val listIntsField by list() override val objectField by obj(::SubMap) + override val valueField by field(MyType) + override val mappedField by field(String::toInt) } override fun container(input: Map): MainClassFields = MapBacked(input)