Skip to content

Commit

Permalink
updates to API for metadatum
Browse files Browse the repository at this point in the history
  • Loading branch information
daviddenton committed Feb 6, 2024
1 parent 4cfbfac commit 9963a24
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 33 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.13.0.0
- **data4k** : Tidying up of Metadatum interfaces

### v2.12.4.0
- **data4k** : Support for Property metadata being stored against properties

Expand Down
26 changes: 26 additions & 0 deletions data4k/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,29 @@ val data: String = input.objectField.stringField
// to write into the structure, just assign to a var
input.objectField.optionalStringField = "hello"
```


## Adding metadata to properties

data4k includes the ability to attach metadata to each property, which can be used for later retrieval from the instance of the container. To use:

```kotlin
// a custom type of metadatum
data class TagMetadatum(val tag: String, val value: String) : MetaDatum

class MetaDataMapContainer(propertySet: Map<String, Any?>) : MapDataContainer(propertySet) {
// create fields as usual, but attach the metadata in the declaration
val stringField: String by required<String>(TagMetadatum("tag", "tagValue"))
}

val container = MetaDataMapContainer(mapOf())

// get all metadata for fields
val metadata: List<PropertyMetadata> = container.propertyMetadata()

// each field can have multiple pieces of metadata and also contains the name and the KType of the field
val metadatum: List<Metadatum> = metadata.data

// get some data from the list..
val tagValue = (metadatum.first() as TagMetadatum).value
```
50 changes: 25 additions & 25 deletions data4k/src/main/kotlin/dev/forkhandles/data/DataContainer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ abstract class DataContainer<CONTENT>(
private val setFn: (CONTENT, String, Any?) -> Unit
) {
/**
* Retrieve the attached metaData data about each of the properties
* Retrieve the attached metadata data about each of the properties
*/
fun propertyMetaData(): List<PropertyMetaData> = kClass().memberProperties
fun propertyMetadata(): List<PropertyMetadata> = kClass().memberProperties
.mapNotNull { prop ->
prop.isAccessible = true
val delegate = prop.getDelegate(this)
when {
delegate is DataProperty<*, *> -> PropertyMetaData(prop.name, prop.returnType, delegate.data)
delegate is DataProperty<*, *> -> PropertyMetadata(prop.name, prop.returnType, delegate.data)
else -> null
}
}
Expand All @@ -40,83 +40,83 @@ abstract class DataContainer<CONTENT>(
protected fun <OUT : Any?, NEXT> required(
mapInFn: (OUT) -> NEXT,
mapOutFn: (NEXT) -> OUT?,
vararg metaData: MetaDatum
vararg metaData: Metadatum
) = property<NEXT, OUT, OUT>(mapInFn, mapOutFn, *metaData)

protected fun <OUT, NEXT> required(mapInFn: (OUT) -> NEXT, vararg metaData: MetaDatum) =
protected fun <OUT, NEXT> required(mapInFn: (OUT) -> NEXT, vararg metaData: Metadatum) =
required(mapInFn, { error("no outbound mapping defined") }, *metaData)

protected fun <OUT : Any> required(vararg metaData: MetaDatum) = required<OUT, OUT>({ it }, { it }, *metaData)
protected fun <OUT : Any> required(vararg metaData: Metadatum) = required<OUT, OUT>({ it }, { it }, *metaData)

protected fun <IN : Any, OUT : Value<IN>> required(factory: ValueFactory<OUT, IN>, vararg metaData: MetaDatum) =
protected fun <IN : Any, OUT : Value<IN>> required(factory: ValueFactory<OUT, IN>, vararg metaData: Metadatum) =
required(factory::of, { it.value }, *metaData)

/** Optional **/

protected fun <OUT, NEXT : Any> optional(
mapInFn: (OUT) -> NEXT,
mapOutFn: (NEXT) -> OUT?,
vararg metaData: MetaDatum
vararg metaData: Metadatum
) =
property<NEXT?, OUT, OUT>(mapInFn, { it?.let(mapOutFn) }, *metaData)

protected fun <OUT, NEXT : Any> optional(mapInFn: (OUT) -> NEXT, vararg metaData: MetaDatum) =
protected fun <OUT, NEXT : Any> optional(mapInFn: (OUT) -> NEXT, vararg metaData: Metadatum) =
optional(mapInFn, { error("no outbound mapping defined") }, *metaData)

protected fun <OUT> optional(vararg metaData: MetaDatum) = property<OUT?, OUT, OUT>({ it }, { it }, *metaData)
protected fun <OUT> optional(vararg metaData: Metadatum) = property<OUT?, OUT, OUT>({ it }, { it }, *metaData)

protected fun <IN : Any, OUT : Value<IN>> optional(factory: ValueFactory<OUT, IN>, vararg metaData: MetaDatum) =
protected fun <IN : Any, OUT : Value<IN>> optional(factory: ValueFactory<OUT, IN>, vararg metaData: Metadatum) =
optional(factory::of, { it.value }, *metaData)

/** Object **/

protected fun <OUT : DataContainer<CONTENT>> obj(
mapInFn: (CONTENT) -> OUT,
mapOutFn: (OUT) -> CONTENT?,
vararg metaData: MetaDatum
vararg metaData: Metadatum
) =
property<OUT, CONTENT, CONTENT>(mapInFn, mapOutFn, *metaData)

protected fun <OUT : DataContainer<CONTENT>> obj(mapInFn: (CONTENT) -> OUT, vararg metaData: MetaDatum) =
protected fun <OUT : DataContainer<CONTENT>> obj(mapInFn: (CONTENT) -> OUT, vararg metaData: Metadatum) =
obj(mapInFn, { it.content }, *metaData)

protected fun <OUT : DataContainer<CONTENT>> optionalObj(mapInFn: (CONTENT) -> OUT, vararg metaData: MetaDatum) =
protected fun <OUT : DataContainer<CONTENT>> optionalObj(mapInFn: (CONTENT) -> OUT, vararg metaData: Metadatum) =
property<OUT?, CONTENT, CONTENT>(mapInFn, { it?.content }, *metaData)

/** List **/

protected fun <OUT, IN> list(
mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?,
vararg metaData: MetaDatum
vararg metaData: Metadatum
) =
property<List<OUT>, List<IN>, List<IN>>({ it.map(mapInFn) }, { it.mapNotNull(mapOutFn) }, *metaData)

protected fun <IN, OUT> list(mapInFn: (IN) -> OUT, vararg metaData: MetaDatum) =
protected fun <IN, OUT> list(mapInFn: (IN) -> OUT, vararg metaData: Metadatum) =
list(mapInFn, { error("no outbound mapping defined") }, *metaData)

protected fun <OUT> list(vararg metaData: MetaDatum) = list<OUT, OUT>({ it }, { it }, *metaData)
protected fun <OUT> list(vararg metaData: Metadatum) = list<OUT, OUT>({ it }, { it }, *metaData)

protected fun <IN : Any, OUT : Value<IN>> list(factory: ValueFactory<OUT, IN>, vararg metaData: MetaDatum) =
protected fun <IN : Any, OUT : Value<IN>> list(factory: ValueFactory<OUT, IN>, vararg metaData: Metadatum) =
list(factory::of, { it.value }, *metaData)

@JvmName("listDataContainer")
protected fun <OUT : DataContainer<CONTENT>?> list(mapInFn: (CONTENT) -> OUT, vararg metaData: MetaDatum) =
protected fun <OUT : DataContainer<CONTENT>?> list(mapInFn: (CONTENT) -> OUT, vararg metaData: Metadatum) =
list(mapInFn, { it?.content }, *metaData)

protected fun <OUT, IN> optionalList(mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?, vararg metaData: MetaDatum) =
protected fun <OUT, IN> optionalList(mapInFn: (IN) -> OUT, mapOutFn: (OUT) -> IN?, vararg metaData: Metadatum) =
property<List<OUT>?, List<IN>, List<IN>>({ it.map(mapInFn) }, { it?.mapNotNull(mapOutFn) }, *metaData)

protected fun <OUT, IN> optionalList(mapInFn: (IN) -> OUT, vararg metaData: MetaDatum) =
protected fun <OUT, IN> optionalList(mapInFn: (IN) -> OUT, vararg metaData: Metadatum) =
optionalList(mapInFn, { error("no outbound mapping defined") }, *metaData)

protected fun <OUT> optionalList(vararg metaData: MetaDatum) =
protected fun <OUT> optionalList(vararg metaData: Metadatum) =
optionalList<OUT, OUT & Any>({ it }, { it }, *metaData)

protected fun <IN : Any, OUT : Value<IN>> optionalList(factory: ValueFactory<OUT, IN>, vararg metaData: MetaDatum) =
protected fun <IN : Any, OUT : Value<IN>> optionalList(factory: ValueFactory<OUT, IN>, vararg metaData: Metadatum) =
optionalList(factory::of, { it.value }, *metaData)

@JvmName("optionalListDataContainer")
protected fun <OUT : DataContainer<CONTENT>?> optionalList(mapInFn: (CONTENT) -> OUT, vararg metaData: MetaDatum) =
protected fun <OUT : DataContainer<CONTENT>?> optionalList(mapInFn: (CONTENT) -> OUT, vararg metaData: Metadatum) =
optionalList(mapInFn, { it?.content }, *metaData)

/** Utility **/
Expand All @@ -137,7 +137,7 @@ abstract class DataContainer<CONTENT>(
private fun <IN, OUT : Any?, OUT2> property(
mapInFn: (OUT) -> IN,
mapOutFn: (IN) -> OUT2?,
vararg metaData: MetaDatum
vararg metaData: Metadatum
) =
DataProperty<DataContainer<CONTENT>, IN>(
{ existsFn(content, it) },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class DataProperty<IN, OUT : Any?>(
private val existsFn: IN.(String) -> Boolean,
private val getFn: IN.(String) -> OUT?,
private val setFn: IN.(String, OUT?) -> Unit,
val data: List<MetaDatum>
val data: List<Metadatum>
) : ReadWriteProperty<IN, OUT> {

@Suppress("UNCHECKED_CAST")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ package dev.forkhandles.data
/**
* Marker interface for a single piece of Property metadata
*/
interface MetaDatum
interface Metadatum
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import kotlin.reflect.KType
/**
* Represents all of the attached metadata for a single property
*/
data class PropertyMetaData(val name: String, val type: KType, val data: List<MetaDatum>)
data class PropertyMetadata(val name: String, val type: KType, val data: List<Metadatum>)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package dev.forkhandles.lens

import dev.forkhandles.data.DataContainer
import dev.forkhandles.data.MetaDatum
import dev.forkhandles.data.PropertyMetaData
import dev.forkhandles.data.Metadatum
import dev.forkhandles.data.PropertyMetadata
import dev.forkhandles.lens.ContainerMeta.bar
import dev.forkhandles.lens.ContainerMeta.foo
import dev.forkhandles.values.IntValue
Expand Down Expand Up @@ -53,7 +53,7 @@ interface MainClassFields<T : SubClassFields> {
var optionalMappedList: List<Int>?
}

enum class ContainerMeta : MetaDatum {
enum class ContainerMeta : Metadatum {
foo, bar
}

Expand Down Expand Up @@ -236,8 +236,8 @@ abstract class DataContainerContract<T : SubClassFields> {
fun `get meta data from the container`() {
val input = container(emptyMap()) as DataContainer<*>

val propertyMetaData = input.propertyMetaData().find { it.name == "string" }
expectThat(propertyMetaData).isEqualTo(PropertyMetaData("string", String::class.starProjectedType, listOf(foo, bar)))
val propertyMetaData = input.propertyMetadata().find { it.name == "string" }
expectThat(propertyMetaData).isEqualTo(PropertyMetadata("string", String::class.starProjectedType, listOf(foo, bar)))
}

private fun <T> expectSetWorks(prop: KMutableProperty0<T>, value: T) {
Expand Down

0 comments on commit 9963a24

Please sign in to comment.